From 35f8fecde6289bd980c7e47ec77d30d7cc3d1845 Mon Sep 17 00:00:00 2001 From: "Mr.Hope" Date: Sat, 17 Oct 2020 21:15:20 +0800 Subject: [PATCH] fix(types): prop type infer, fix #555 (#561) --- src/component/componentProps.ts | 37 ++++++++++++----------------- src/component/componentProxy.ts | 4 ++-- test-dts/defineComponent.test-d.ts | 38 ++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/component/componentProps.ts b/src/component/componentProps.ts index 7d03c0f6..b4040580 100644 --- a/src/component/componentProps.ts +++ b/src/component/componentProps.ts @@ -8,14 +8,14 @@ export type ComponentObjectPropsOptions

= { [K in keyof P]: Prop | null } -export type Prop = PropOptions | PropType +export type Prop = PropOptions | PropType type DefaultFactory = () => T | null | undefined -export interface PropOptions { +export interface PropOptions { type?: PropType | true | null required?: boolean - default?: T | DefaultFactory | null | undefined + default?: D | DefaultFactory | null | undefined | object validator?(value: unknown): boolean } @@ -26,18 +26,11 @@ type PropConstructor = | { (): T } | { new (...args: string[]): Function } -type RequiredKeys = { - [K in keyof T]: T[K] extends - | { required: true } - | (MakeDefaultRequired extends true ? { default: any } : never) - ? K - : never +type RequiredKeys = { + [K in keyof T]: T[K] extends { required: true } | { default: any } ? K : never }[keyof T] -type OptionalKeys = Exclude< - keyof T, - RequiredKeys -> +type OptionalKeys = Exclude> type ExtractFunctionPropType< T extends Function, @@ -55,18 +48,18 @@ type InferPropType = T extends null : T extends { type: null | true } ? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean` : T extends ObjectConstructor | { type: ObjectConstructor } - ? { [key: string]: any } + ? Record : T extends BooleanConstructor | { type: BooleanConstructor } ? boolean : T extends FunctionConstructor ? Function - : T extends Prop - ? ExtractCorrectPropType : T; + : T extends Prop + ? unknown extends V + ? D + : ExtractCorrectPropType + : T -export type ExtractPropTypes< - O, - MakeDefaultRequired extends boolean = true -> = O extends object - ? { [K in RequiredKeys]: InferPropType } & - { [K in OptionalKeys]?: InferPropType } +export type ExtractPropTypes = O extends object + ? { [K in RequiredKeys]: InferPropType } & + { [K in OptionalKeys]?: InferPropType } : { [K in string]: any } diff --git a/src/component/componentProxy.ts b/src/component/componentProxy.ts index 853218a1..d9105f16 100644 --- a/src/component/componentProxy.ts +++ b/src/component/componentProxy.ts @@ -39,7 +39,7 @@ type VueConstructorProxy = VueConstructor & { new (...args: any[]): ComponentRenderProxy< ExtractPropTypes, ShallowUnwrapRef, - ExtractPropTypes + ExtractPropTypes > } @@ -59,6 +59,6 @@ export type VueProxy< Methods, Computed, PropsOptions, - ExtractPropTypes + ExtractPropTypes > & VueConstructorProxy diff --git a/test-dts/defineComponent.test-d.ts b/test-dts/defineComponent.test-d.ts index 8ea97c84..6cc8b8a9 100644 --- a/test-dts/defineComponent.test-d.ts +++ b/test-dts/defineComponent.test-d.ts @@ -14,6 +14,7 @@ describe('with object props', () => { b: string e?: Function bb: string + bbb: string cc?: string[] | undefined dd: { n: 1 } ee?: () => string @@ -23,6 +24,9 @@ describe('with object props', () => { eee: () => { a: string } fff: (a: number, b: string) => { a: boolean } hhh: boolean + ggg: 'foo' | 'bar' + ffff: (a: number, b: string) => { a: boolean } + validated?: string } type GT = string & { __brand: unknown } @@ -40,6 +44,11 @@ describe('with object props', () => { bb: { default: 'hello', }, + bbb: { + // Note: default function value requires arrow syntax + explicit + // annotation + default: (props: any) => (props.bb as string) || 'foo', + }, // explicit type casting cc: Array as PropType, // required + type casting @@ -68,10 +77,25 @@ describe('with object props', () => { type: Function as PropType<(a: number, b: string) => { a: boolean }>, required: true, }, + // default + type casting + ggg: { + type: String as PropType<'foo' | 'bar'>, + default: 'foo', + }, hhh: { type: Boolean, required: true, }, + // default + function + ffff: { + type: Function as PropType<(a: number, b: string) => { a: boolean }>, + default: (_a: number, _b: string) => ({ a: true }), + }, + validated: { + type: String, + // validator requires explicit annotation + validator: (val: unknown) => val !== '', + }, }, setup(props) { // type assertion. See https://github.com/SamVerschueren/tsd @@ -83,11 +107,15 @@ describe('with object props', () => { expectType(props.dd) expectType(props.ee) expectType(props.ff) + expectType(props.bbb) expectType(props.ccc) expectType(props.ddd) expectType(props.eee) expectType(props.fff) + expectType(props.ggg) expectType(props.hhh) + expectType(props.ffff) + expectType(props.validated) isNotAnyOrUndefined(props.a) isNotAnyOrUndefined(props.b) @@ -97,11 +125,14 @@ describe('with object props', () => { isNotAnyOrUndefined(props.dd) isNotAnyOrUndefined(props.ee) isNotAnyOrUndefined(props.ff) + isNotAnyOrUndefined(props.bbb) isNotAnyOrUndefined(props.ccc) isNotAnyOrUndefined(props.ddd) isNotAnyOrUndefined(props.eee) isNotAnyOrUndefined(props.fff) + isNotAnyOrUndefined(props.ggg) isNotAnyOrUndefined(props.hhh) + isNotAnyOrUndefined(props.ffff) expectError((props.a = 1)) @@ -126,11 +157,15 @@ describe('with object props', () => { expectType(props.dd) expectType(props.ee) expectType(props.ff) + expectType(props.bbb) expectType(props.ccc) expectType(props.ddd) expectType(props.eee) expectType(props.fff) + expectType(props.ggg) expectType(props.hhh) + expectType(props.ffff) + expectType(props.validated) // @ts-expect-error props should be readonly expectError((props.a = 1)) @@ -144,10 +179,13 @@ describe('with object props', () => { expectType(this.dd) expectType(this.ee) expectType(this.ff) + expectType(this.bbb) expectType(this.ccc) expectType(this.ddd) expectType(this.eee) expectType(this.fff) + expectType(this.ggg) + expectType(this.ffff) expectType(this.hhh) // @ts-expect-error props on `this` should be readonly