Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions test/behavior.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expectType } from 'tsd'
import { expectAssignable, expectType } from 'tsd'

expectType<string & {}>(Behavior({}))
expectAssignable<string>(Behavior({}))

Behavior({
behaviors: [],
Expand Down
49 changes: 47 additions & 2 deletions test/component.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expectType, expectError, expectNotAssignable } from 'tsd'
import { expectType, expectError, expectNotAssignable, expectAssignable } from 'tsd'

expectType<string>(Component({}))
expectAssignable<string>(Component({}))

Component({
behaviors: [''],
Expand Down Expand Up @@ -194,6 +194,10 @@ Component({
type: Number,
value: 1,
},
n3: {
type: Number,
value: 1 as const,
},
s: String,
a: Array,
a2: {
Expand All @@ -206,6 +210,12 @@ Component({
type: Object,
value: {} as Record<string, any>,
},
o3: {
type: Object,
value: {
f1: 123,
},
},
},
methods: {
g() {
Expand All @@ -216,12 +226,15 @@ Component({
expectType<string>(this.g())
expectType<number>(this.data.n)
expectType<number>(this.data.n2)
expectType<1>(this.data.n3)
expectType<string>(this.data.s)
expectType<any[]>(this.data.a)
expectType<number[]>(this.data.a2)
expectType<boolean>(this.data.b)
expectType<Record<string, any>>(this.data.o)
expectType<Record<string, any>>(this.data.o2)
expectType<number>(this.data.o3.f1)
expectError(this.data.o3.f2)
expectType<any>(this.data.o2.city)
expectType<any>(this.data.a[0])
expectType<any>(this.data.o.prop)
Expand Down Expand Up @@ -511,3 +524,35 @@ Component<{}, {}, { fn(): void }, []>({
})
}

{
const def = Component({
properties: {
a: Boolean,
},
data: {
b: 1,
},
methods: {
c() {}
},
})
type FieldTypes = (typeof def)['_$fieldTypes']
expectType<FieldTypes['propertyValues']['a']>(false as boolean)
expectType<FieldTypes['dataWithProperties']['a']>(false as boolean)
expectType<FieldTypes['dataWithProperties']['b']>(1 as number)
expectType<FieldTypes['methods']['c']>(() => {})
}

{
type CustomProperties = {
customProp: string
}
Component<{}, {}, {}, [], CustomProperties>({
lifetimes: {
created() {
this.customProp = 'customProp'
}
}
})
}

18 changes: 15 additions & 3 deletions test/page.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expectType, expectError, expectNotAssignable } from 'tsd'
import { expectType, expectError, expectNotAssignable, expectAssignable } from 'tsd'

expectType<void>(Page({}))
expectAssignable<void>(Page({}))

expectType<Record<string, any>>(getCurrentPages()[0].data)

Expand Down Expand Up @@ -241,4 +241,16 @@ Page({
onShareAppMessage(): WechatMiniprogram.Page.ICustomShareContent {
return { title: this.data.a, imageUrl: '', path: '' }
},
})
})

{
const def = Page({
data: {
b: 1,
},
c() {},
})
type FieldTypes = (typeof def)['_$fieldTypes']
expectType<FieldTypes['dataWithProperties']['b']>(1 as number)
expectType<FieldTypes['methods']['c']>(() => {})
}
30 changes: 22 additions & 8 deletions types/wx/lib.wx.behavior.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,7 @@ declare namespace WechatMiniprogram.Behavior {
TProperty extends PropertyOption = {},
TMethod extends MethodOption = {},
TBehavior extends BehaviorOption = []
> = string & {
[key in 'BehaviorType']?: {
data: Component.FilterUnknownType<TData> & Component.MixinData<TBehavior>
properties: Component.FilterUnknownType<TProperty> & Component.MixinProperties<TBehavior, true>
methods: Component.FilterUnknownType<TMethod> & Component.MixinMethods<TBehavior>
}
}
> = string & BehaviorTypeSignature<TData, TProperty, TMethod, TBehavior>
type Instance<
TData extends DataOption,
TProperty extends PropertyOption,
Expand Down Expand Up @@ -65,7 +59,7 @@ declare namespace WechatMiniprogram.Behavior {
TCustomInstanceProperty extends IAnyObject = Record<string, never>
>(
options: Options<TData, TProperty, TMethod, TBehavior, TCustomInstanceProperty>
): BehaviorIdentifier<TData, TProperty, TMethod, TBehavior>
): string & BehaviorTypeSignature<TData, TProperty, TMethod, TBehavior>
}

type DataOption = Component.DataOption
Expand All @@ -80,6 +74,26 @@ declare namespace WechatMiniprogram.Behavior {
type DefinitionFilter = Component.DefinitionFilter
type Lifetimes = Component.Lifetimes
type OtherOption = Omit<Component.OtherOption, 'options'>

/** 用于辅助识别 behavior 字段类型的虚拟字段 */
class BehaviorTypeSignature<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends MethodOption,
TBehavior extends BehaviorOption,
> {
protected readonly _$behaviorFieldTypes?: BehaviorTypeSignatureFields<TData, TProperty, TMethod, TBehavior>
}
type BehaviorTypeSignatureFields<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends MethodOption,
TBehavior extends BehaviorOption,
> = {
data: Component.FilterUnknownType<TData> & Component.MixinData<TBehavior>
properties: Component.FilterUnknownType<TProperty> & Component.MixinProperties<TBehavior, true>
methods: Component.FilterUnknownType<TMethod> & Component.MixinMethods<TBehavior>
}
}
/** 注册一个 `behavior`,接受一个 `Object` 类型的参数。*/
declare let Behavior: WechatMiniprogram.Behavior.Constructor
62 changes: 50 additions & 12 deletions types/wx/lib.wx.component.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,17 @@ declare namespace WechatMiniprogram.Component {
TCustomInstanceProperty,
TIsPage
>
): string
): string & ComponentTypeSignature<TData, TProperty, TMethod>
}
type DataOption = Record<string, any>
type PropertyOption = Record<string, AllProperty>
type MethodOption = Record<string, Function>

type BehaviorOption = Behavior.BehaviorIdentifier[]
type ExtractBehaviorType<T> = T extends { BehaviorType?: infer B } ? B : never
type BehaviorFieldTypes<T> = (T & Record<string, unknown>)['_$behaviorFieldTypes']
type ExtractBehaviorType<T> = BehaviorFieldTypes<T> extends Behavior.BehaviorTypeSignatureFields<any, any, any, any> | undefined
? BehaviorFieldTypes<T>
: never
type ExtractData<T> = T extends { data: infer D } ? D : never
type ExtractProperties<T, TIsBehavior extends boolean = false> = T extends { properties: infer P } ?
TIsBehavior extends true ? P : PropertyOptionToData<P extends PropertyOption ? P : {}> : never
Expand All @@ -111,6 +114,19 @@ declare namespace WechatMiniprogram.Component {
type MixinProperties<T extends any[], TIsBehavior extends boolean = false> = UnionToIntersection<ExtractProperties<ExtractBehaviorType<T[number]>, TIsBehavior>>
type MixinMethods<T extends any[]> = UnionToIntersection<ExtractMethods<ExtractBehaviorType<T[number]>>>

/** 用于辅助识别组件类型的虚拟字段(供 glass-easel-analyzer 等外部模块使用) */
class ComponentTypeSignature<
TData extends DataOption,
TProperty extends PropertyOption,
TMethod extends MethodOption,
> {
protected readonly _$fieldTypes: {
propertyValues: PropertyOptionToData<TProperty>
dataWithProperties: TData & PropertyOptionToData<TProperty>
methods: TMethod
}
}

interface Behavior<B extends BehaviorOption> {
/** 类似于mixins和traits的组件间代码复用机制,参见 [behaviors](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html) */
behaviors?: B
Expand All @@ -128,6 +144,8 @@ declare namespace WechatMiniprogram.Component {
/** 组件的方法,包括事件响应函数和任意的自定义方法,关于事件响应函数的使用,参见 [组件间通信与事件](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.html) */
methods: M & (TIsPage extends true ? Partial<Page.ILifetime> : {})
}

type Satisfy<T, V> = V extends T ? V : T
type PropertyType =
| StringConstructor
| NumberConstructor
Expand All @@ -148,11 +166,24 @@ declare namespace WechatMiniprogram.Component {
: T extends ObjectConstructor
? IAnyObject
: never
type FullProperty<T extends PropertyType> = {
type SimpleValueType<T extends PropertyType, V> = T extends StringConstructor
? Satisfy<string, V>
: T extends NumberConstructor
? Satisfy<number, V>
: T extends BooleanConstructor
? Satisfy<boolean, V>
: T extends ArrayConstructor
? Satisfy<any[], V>
: T extends ObjectConstructor
? Satisfy<Record<string, any> | null, V>
: T extends FunctionConstructor
? Satisfy<(...args: any[]) => any, V>
: never
type FullProperty<T extends PropertyType, V extends ValueType<T>> = {
/** 属性类型 */
type: T
/** 属性初始值 */
value?: ValueType<T>
value?: V
/** 属性值被更改时的响应函数 */
observer?:
| string
Expand All @@ -165,12 +196,12 @@ declare namespace WechatMiniprogram.Component {
optionalTypes?: ShortProperty[]
}
type AllFullProperty =
| FullProperty<StringConstructor>
| FullProperty<NumberConstructor>
| FullProperty<BooleanConstructor>
| FullProperty<ArrayConstructor>
| FullProperty<ObjectConstructor>
| FullProperty<null>
| FullProperty<StringConstructor, any>
| FullProperty<NumberConstructor, any>
| FullProperty<BooleanConstructor, any>
| FullProperty<ArrayConstructor, any>
| FullProperty<ObjectConstructor, any>
| FullProperty<null, any>
type ShortProperty =
| StringConstructor
| NumberConstructor
Expand All @@ -182,8 +213,15 @@ declare namespace WechatMiniprogram.Component {
type PropertyToData<T extends AllProperty> = T extends ShortProperty
? ValueType<T>
: FullPropertyToData<Exclude<T, ShortProperty>>
type ArrayOrObject = ArrayConstructor | ObjectConstructor
type FullPropertyToData<T extends AllFullProperty> = T['type'] extends ArrayOrObject ? unknown extends T['value'] ? ValueType<T['type']> : T['value'] : ValueType<T['type']>
type FullPropertyToData<T> = T extends FullProperty<infer T, infer V>
? unknown extends V
? ValueType<T>
: ((a: T) => void) extends (a: PropertyType) => void
? V
: V extends ValueType<T>
? SimpleValueType<T, V>
: never
: never
type PropertyOptionToData<P extends PropertyOption> = {
[name in keyof P]: PropertyToData<P[name]>
}
Expand Down
6 changes: 5 additions & 1 deletion types/wx/lib.wx.page.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ declare namespace WechatMiniprogram.Page {
interface Constructor {
<TData extends DataOption, TCustom extends CustomOption>(
options: Options<TData, TCustom>
): void
): void & Component.ComponentTypeSignature<
TData,
Record<never, never>,
{ [K in keyof TCustom as TCustom[K] extends Function ? K : never]: TCustom[K] }
>
}
interface ILifetime {
/** 生命周期回调—监听页面加载
Expand Down