Skip to content

Commit 35f8fec

Browse files
authored
fix(types): prop type infer, fix #555 (#561)
1 parent ded5ab7 commit 35f8fec

File tree

3 files changed

+55
-24
lines changed

3 files changed

+55
-24
lines changed

src/component/componentProps.ts

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ export type ComponentObjectPropsOptions<P = Data> = {
88
[K in keyof P]: Prop<P[K]> | null
99
}
1010

11-
export type Prop<T> = PropOptions<T> | PropType<T>
11+
export type Prop<T, D = T> = PropOptions<T, D> | PropType<T>
1212

1313
type DefaultFactory<T> = () => T | null | undefined
1414

15-
export interface PropOptions<T = any> {
15+
export interface PropOptions<T = any, D = T> {
1616
type?: PropType<T> | true | null
1717
required?: boolean
18-
default?: T | DefaultFactory<T> | null | undefined
18+
default?: D | DefaultFactory<D> | null | undefined | object
1919
validator?(value: unknown): boolean
2020
}
2121

@@ -26,18 +26,11 @@ type PropConstructor<T> =
2626
| { (): T }
2727
| { new (...args: string[]): Function }
2828

29-
type RequiredKeys<T, MakeDefaultRequired> = {
30-
[K in keyof T]: T[K] extends
31-
| { required: true }
32-
| (MakeDefaultRequired extends true ? { default: any } : never)
33-
? K
34-
: never
29+
type RequiredKeys<T> = {
30+
[K in keyof T]: T[K] extends { required: true } | { default: any } ? K : never
3531
}[keyof T]
3632

37-
type OptionalKeys<T, MakeDefaultRequired> = Exclude<
38-
keyof T,
39-
RequiredKeys<T, MakeDefaultRequired>
40-
>
33+
type OptionalKeys<T> = Exclude<keyof T, RequiredKeys<T>>
4134

4235
type ExtractFunctionPropType<
4336
T extends Function,
@@ -55,18 +48,18 @@ type InferPropType<T> = T extends null
5548
: T extends { type: null | true }
5649
? 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`
5750
: T extends ObjectConstructor | { type: ObjectConstructor }
58-
? { [key: string]: any }
51+
? Record<string, any>
5952
: T extends BooleanConstructor | { type: BooleanConstructor }
6053
? boolean
6154
: T extends FunctionConstructor
6255
? Function
63-
: T extends Prop<infer V>
64-
? ExtractCorrectPropType<V> : T;
56+
: T extends Prop<infer V, infer D>
57+
? unknown extends V
58+
? D
59+
: ExtractCorrectPropType<V>
60+
: T
6561

66-
export type ExtractPropTypes<
67-
O,
68-
MakeDefaultRequired extends boolean = true
69-
> = O extends object
70-
? { [K in RequiredKeys<O, MakeDefaultRequired>]: InferPropType<O[K]> } &
71-
{ [K in OptionalKeys<O, MakeDefaultRequired>]?: InferPropType<O[K]> }
62+
export type ExtractPropTypes<O> = O extends object
63+
? { [K in RequiredKeys<O>]: InferPropType<O[K]> } &
64+
{ [K in OptionalKeys<O>]?: InferPropType<O[K]> }
7265
: { [K in string]: any }

src/component/componentProxy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type VueConstructorProxy<PropsOptions, RawBindings> = VueConstructor & {
3939
new (...args: any[]): ComponentRenderProxy<
4040
ExtractPropTypes<PropsOptions>,
4141
ShallowUnwrapRef<RawBindings>,
42-
ExtractPropTypes<PropsOptions, false>
42+
ExtractPropTypes<PropsOptions>
4343
>
4444
}
4545

@@ -59,6 +59,6 @@ export type VueProxy<
5959
Methods,
6060
Computed,
6161
PropsOptions,
62-
ExtractPropTypes<PropsOptions, false>
62+
ExtractPropTypes<PropsOptions>
6363
> &
6464
VueConstructorProxy<PropsOptions, RawBindings>

test-dts/defineComponent.test-d.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ describe('with object props', () => {
1414
b: string
1515
e?: Function
1616
bb: string
17+
bbb: string
1718
cc?: string[] | undefined
1819
dd: { n: 1 }
1920
ee?: () => string
@@ -23,6 +24,9 @@ describe('with object props', () => {
2324
eee: () => { a: string }
2425
fff: (a: number, b: string) => { a: boolean }
2526
hhh: boolean
27+
ggg: 'foo' | 'bar'
28+
ffff: (a: number, b: string) => { a: boolean }
29+
validated?: string
2630
}
2731

2832
type GT = string & { __brand: unknown }
@@ -40,6 +44,11 @@ describe('with object props', () => {
4044
bb: {
4145
default: 'hello',
4246
},
47+
bbb: {
48+
// Note: default function value requires arrow syntax + explicit
49+
// annotation
50+
default: (props: any) => (props.bb as string) || 'foo',
51+
},
4352
// explicit type casting
4453
cc: Array as PropType<string[]>,
4554
// required + type casting
@@ -68,10 +77,25 @@ describe('with object props', () => {
6877
type: Function as PropType<(a: number, b: string) => { a: boolean }>,
6978
required: true,
7079
},
80+
// default + type casting
81+
ggg: {
82+
type: String as PropType<'foo' | 'bar'>,
83+
default: 'foo',
84+
},
7185
hhh: {
7286
type: Boolean,
7387
required: true,
7488
},
89+
// default + function
90+
ffff: {
91+
type: Function as PropType<(a: number, b: string) => { a: boolean }>,
92+
default: (_a: number, _b: string) => ({ a: true }),
93+
},
94+
validated: {
95+
type: String,
96+
// validator requires explicit annotation
97+
validator: (val: unknown) => val !== '',
98+
},
7599
},
76100
setup(props) {
77101
// type assertion. See https://github.com/SamVerschueren/tsd
@@ -83,11 +107,15 @@ describe('with object props', () => {
83107
expectType<ExpectedProps['dd']>(props.dd)
84108
expectType<ExpectedProps['ee']>(props.ee)
85109
expectType<ExpectedProps['ff']>(props.ff)
110+
expectType<ExpectedProps['bbb']>(props.bbb)
86111
expectType<ExpectedProps['ccc']>(props.ccc)
87112
expectType<ExpectedProps['ddd']>(props.ddd)
88113
expectType<ExpectedProps['eee']>(props.eee)
89114
expectType<ExpectedProps['fff']>(props.fff)
115+
expectType<ExpectedProps['ggg']>(props.ggg)
90116
expectType<ExpectedProps['hhh']>(props.hhh)
117+
expectType<ExpectedProps['ffff']>(props.ffff)
118+
expectType<ExpectedProps['validated']>(props.validated)
91119

92120
isNotAnyOrUndefined(props.a)
93121
isNotAnyOrUndefined(props.b)
@@ -97,11 +125,14 @@ describe('with object props', () => {
97125
isNotAnyOrUndefined(props.dd)
98126
isNotAnyOrUndefined(props.ee)
99127
isNotAnyOrUndefined(props.ff)
128+
isNotAnyOrUndefined(props.bbb)
100129
isNotAnyOrUndefined(props.ccc)
101130
isNotAnyOrUndefined(props.ddd)
102131
isNotAnyOrUndefined(props.eee)
103132
isNotAnyOrUndefined(props.fff)
133+
isNotAnyOrUndefined(props.ggg)
104134
isNotAnyOrUndefined(props.hhh)
135+
isNotAnyOrUndefined(props.ffff)
105136

106137
expectError((props.a = 1))
107138

@@ -126,11 +157,15 @@ describe('with object props', () => {
126157
expectType<ExpectedProps['dd']>(props.dd)
127158
expectType<ExpectedProps['ee']>(props.ee)
128159
expectType<ExpectedProps['ff']>(props.ff)
160+
expectType<ExpectedProps['bbb']>(props.bbb)
129161
expectType<ExpectedProps['ccc']>(props.ccc)
130162
expectType<ExpectedProps['ddd']>(props.ddd)
131163
expectType<ExpectedProps['eee']>(props.eee)
132164
expectType<ExpectedProps['fff']>(props.fff)
165+
expectType<ExpectedProps['ggg']>(props.ggg)
133166
expectType<ExpectedProps['hhh']>(props.hhh)
167+
expectType<ExpectedProps['ffff']>(props.ffff)
168+
expectType<ExpectedProps['validated']>(props.validated)
134169

135170
// @ts-expect-error props should be readonly
136171
expectError((props.a = 1))
@@ -144,10 +179,13 @@ describe('with object props', () => {
144179
expectType<ExpectedProps['dd']>(this.dd)
145180
expectType<ExpectedProps['ee']>(this.ee)
146181
expectType<ExpectedProps['ff']>(this.ff)
182+
expectType<ExpectedProps['bbb']>(this.bbb)
147183
expectType<ExpectedProps['ccc']>(this.ccc)
148184
expectType<ExpectedProps['ddd']>(this.ddd)
149185
expectType<ExpectedProps['eee']>(this.eee)
150186
expectType<ExpectedProps['fff']>(this.fff)
187+
expectType<ExpectedProps['ggg']>(this.ggg)
188+
expectType<ExpectedProps['ffff']>(this.ffff)
151189
expectType<ExpectedProps['hhh']>(this.hhh)
152190

153191
// @ts-expect-error props on `this` should be readonly

0 commit comments

Comments
 (0)