为什么常规函数会破坏类型推断?
Why does regular function break type inference?
我有一个 defineComponent()
函数,它接受一个对象作为参数。该对象可以包含一个 props
属性 和一个 setup()
属性,它接收一个 props
参数,其类型是从 props
属性 早前在对象中声明。
示例:
defineComponent({
props: {
size: {
type: String,
default: 'lg',
validator: (value: string) => true,
}
},
setup(props) {
console.log(props.size.charAt(0))
}
})
奇怪的是,如果 props.size.validator
声明为常规函数 (validator: function(value: string) { return true }
) 而不是箭头函数,则 props
参数的推断类型是完全错误的。对于上面的例子。应该是:
{
size: string
}
但是是这样的:
string[] | {
[x: string]: Prop<unknown, unknown> | null;
}
常规函数与工作代码中的箭头函数基本相同,所以我没想到它会破坏类型推断。
为什么常规函数会破坏 setup()
的 props
参数的类型推断?
mylib.d.ts:
declare interface PropOptions<T = any, D = T> {
type?: PropType<T>;
required?: boolean;
default?: D | DefaultFactory<D>;
validator?(value: unknown): boolean;
}
declare type DefaultFactory<T> = (props: Data) => T;
declare type Data = Record<string, unknown>;
export declare type PropType<T> = PropConstructor<T> | PropConstructor<T>[];
declare type PropConstructor<T = any> = { new (...args: any[]): T & {} };
export declare type Prop<T, D = T> = PropOptions<T, D> | PropType<T>;
export declare type ComponentPropsOptions<P = Data> = ComponentObjectPropsOptions<P> | string[];
export declare type ComponentObjectPropsOptions<P = Data> = { [K in keyof P]: Prop<P[K]> | null };
declare type InferPropType<T> =
[T] extends [null] ? any
: [T] extends [{ type: null | true }] ? any
: [T] extends [ObjectConstructor | { type: ObjectConstructor }] ? Record<string, any>
: [T] extends [BooleanConstructor | { type: BooleanConstructor }] ? boolean
: [T] extends [DateConstructor | { type: DateConstructor }] ? Date
: [T] extends [Prop<infer V, infer D>] ? (unknown extends V ? D : V)
: T;
export declare type ExtractPropTypes<O> =
O extends object
? { [K in keyof O]: InferPropType<O[K]> }
: { [K in string]: any };
export declare interface ComponentOptionsBase<Props> {
setup?: (this: void, props: Props) => any;
}
export declare type ComponentOptionsWithObjectProps<PropsOptions = ComponentObjectPropsOptions, Props = ExtractPropTypes<PropsOptions>>
= ComponentOptionsBase<Props> & { props: PropsOptions };
export declare function defineComponent<PropsOptions extends ComponentPropsOptions>(options: ComponentOptionsWithObjectProps<PropsOptions>): any;
更新
type Constructors =
| BooleanConstructor
| ObjectConstructor
| StringConstructor
type DefaultFactory<T> = (props: Data) => T;
type Data = Record<string, unknown>;
type Entity<T extends Constructors> = {
type: T,
default: InstanceType<T> | DefaultFactory<T>
validator: (value: ConstructorMappings<T>) => boolean
}
type ConstructorMappings<T extends Constructors> =
T extends StringConstructor
? string
: T extends BooleanConstructor
? boolean
: T extends ObjectConstructor
? object
: never;
type MakeSetup<Props extends Record<string, Entity<Constructors>>>={
[Prop in keyof Props]:ConstructorMappings<Props[Prop]['type']>
}
interface Props<T extends Constructors> {
props: {
size: Entity<T>
},
setup: (arg: MakeSetup<this['props']>) => any
}
export declare function defineComponent<C extends Constructors>(options: Props<C>): any;
defineComponent({
props: {
size: {
type: String,
default: 'lg',
validator: (value /** infered as a string */) => true,
}
},
setup:(props)=> {
console.log(props.size.charAt(0)) // ok
}
})
如您所见,现在推断出 value
参数。
事实证明这是 TypeScript 的设计限制,因为 explained by TypeScript's lead architect, Anders Hejlberg:
ahejlsberg commented on Jun 29, 2020
This is a design limitation. Similar to #38872. A arrow function with no parameters is not context sensitive, but a function expression with no parameters is context sensitive because of the implicit this
parameter. Anything that is context sensitive is excluded from the first phase of type inference, which is the phase that determines the types we'll use for contextually typed parameters. So, in the original example, when the value for the a property is an arrow function, we succeed in making an inference for A
before we assign a contextual type to the a
parameter of b
. But when the value is a function expression, we make no inferences and the a
parameter is given type unknown
.
我有一个 defineComponent()
函数,它接受一个对象作为参数。该对象可以包含一个 props
属性 和一个 setup()
属性,它接收一个 props
参数,其类型是从 props
属性 早前在对象中声明。
示例:
defineComponent({
props: {
size: {
type: String,
default: 'lg',
validator: (value: string) => true,
}
},
setup(props) {
console.log(props.size.charAt(0))
}
})
奇怪的是,如果 props.size.validator
声明为常规函数 (validator: function(value: string) { return true }
) 而不是箭头函数,则 props
参数的推断类型是完全错误的。对于上面的例子。应该是:
{
size: string
}
但是是这样的:
string[] | {
[x: string]: Prop<unknown, unknown> | null;
}
常规函数与工作代码中的箭头函数基本相同,所以我没想到它会破坏类型推断。
为什么常规函数会破坏 setup()
的 props
参数的类型推断?
mylib.d.ts:
declare interface PropOptions<T = any, D = T> {
type?: PropType<T>;
required?: boolean;
default?: D | DefaultFactory<D>;
validator?(value: unknown): boolean;
}
declare type DefaultFactory<T> = (props: Data) => T;
declare type Data = Record<string, unknown>;
export declare type PropType<T> = PropConstructor<T> | PropConstructor<T>[];
declare type PropConstructor<T = any> = { new (...args: any[]): T & {} };
export declare type Prop<T, D = T> = PropOptions<T, D> | PropType<T>;
export declare type ComponentPropsOptions<P = Data> = ComponentObjectPropsOptions<P> | string[];
export declare type ComponentObjectPropsOptions<P = Data> = { [K in keyof P]: Prop<P[K]> | null };
declare type InferPropType<T> =
[T] extends [null] ? any
: [T] extends [{ type: null | true }] ? any
: [T] extends [ObjectConstructor | { type: ObjectConstructor }] ? Record<string, any>
: [T] extends [BooleanConstructor | { type: BooleanConstructor }] ? boolean
: [T] extends [DateConstructor | { type: DateConstructor }] ? Date
: [T] extends [Prop<infer V, infer D>] ? (unknown extends V ? D : V)
: T;
export declare type ExtractPropTypes<O> =
O extends object
? { [K in keyof O]: InferPropType<O[K]> }
: { [K in string]: any };
export declare interface ComponentOptionsBase<Props> {
setup?: (this: void, props: Props) => any;
}
export declare type ComponentOptionsWithObjectProps<PropsOptions = ComponentObjectPropsOptions, Props = ExtractPropTypes<PropsOptions>>
= ComponentOptionsBase<Props> & { props: PropsOptions };
export declare function defineComponent<PropsOptions extends ComponentPropsOptions>(options: ComponentOptionsWithObjectProps<PropsOptions>): any;
更新
type Constructors =
| BooleanConstructor
| ObjectConstructor
| StringConstructor
type DefaultFactory<T> = (props: Data) => T;
type Data = Record<string, unknown>;
type Entity<T extends Constructors> = {
type: T,
default: InstanceType<T> | DefaultFactory<T>
validator: (value: ConstructorMappings<T>) => boolean
}
type ConstructorMappings<T extends Constructors> =
T extends StringConstructor
? string
: T extends BooleanConstructor
? boolean
: T extends ObjectConstructor
? object
: never;
type MakeSetup<Props extends Record<string, Entity<Constructors>>>={
[Prop in keyof Props]:ConstructorMappings<Props[Prop]['type']>
}
interface Props<T extends Constructors> {
props: {
size: Entity<T>
},
setup: (arg: MakeSetup<this['props']>) => any
}
export declare function defineComponent<C extends Constructors>(options: Props<C>): any;
defineComponent({
props: {
size: {
type: String,
default: 'lg',
validator: (value /** infered as a string */) => true,
}
},
setup:(props)=> {
console.log(props.size.charAt(0)) // ok
}
})
如您所见,现在推断出 value
参数。
事实证明这是 TypeScript 的设计限制,因为 explained by TypeScript's lead architect, Anders Hejlberg:
ahejlsberg commented on Jun 29, 2020
This is a design limitation. Similar to #38872. A arrow function with no parameters is not context sensitive, but a function expression with no parameters is context sensitive because of the implicit
this
parameter. Anything that is context sensitive is excluded from the first phase of type inference, which is the phase that determines the types we'll use for contextually typed parameters. So, in the original example, when the value for the a property is an arrow function, we succeed in making an inference forA
before we assign a contextual type to thea
parameter ofb
. But when the value is a function expression, we make no inferences and thea
parameter is given typeunknown
.