【问题标题】:Why does regular function break type inference?为什么常规函数会破坏类型推断?
【发布时间】:2021-09-09 21:59:37
【问题描述】:

我有一个将对象作为参数的defineComponent() 函数。该对象可以包含一个props 属性和一个接收props 参数的setup() 属性,该参数的类型是从该对象之前声明的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;

Playground

【问题讨论】:

  • 我认为可能是这种情况github.com/microsoft/TypeScript/issues/43948 但他是我的第一个猜测,因为示例非常复杂
  • 你想达到什么目的?我相信validator 方法应该根据type 属性推断参数类型。我认为对validator 参数使用显式类型是错误的。 validator 参数被推断为 unknown 的事实可能是一个错误。这只是我的看法
  • @captain-yossarian 感谢您提供问题链接。这可能是这里的问题。
  • @captain-yossarian 目标是从先前声明的props 属性中推断出setup() 中的props 参数。我同意打字中可能存在一些错误,我有兴趣修复它。

标签: typescript type-inference


【解决方案1】:

更新

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 参数现在已被推断出来。

【讨论】:

  • 谢谢。然而,这并不是我所需要的。 props 属性和 props 参数不应该是相同的类型。 props 参数是根据 props 属性的声明创建的。由于size 具有typeString(构造函数),因此props 参数应包含size 类型的size 属性string(类型)。实际上有多种方法可以声明props,但这可能超出了问题的范围。
  • 供参考,defineComponentsetup() 来自Vue's Composition APIprops 属性是 Vue's Options API 的一部分。
  • 我更新了。请让我知道这对你有没有用。但是,我愿意打赌 Vue 应该对此有支持
【解决方案2】:

原来这是 TypeScript 的设计限制,如explained by TypeScript's lead architect, Anders Hejlberg:

ahejlsberg评论了Jun 29, 2020

这是一个设计限制。类似于#38872。没有参数的箭头函数是上下文不敏感的,但是没有参数的函数表达式是上下文敏感的,因为隐含的this 参数。任何与上下文相关的内容都被排除在类型推断的第一阶段之外,该阶段确定我们将用于上下文类型参数的类型。因此,在原始示例中,当 a 属性的值是箭头函数时,我们在将上下文类型分配给 ba 参数之前成功地推断出 A。但是当值是函数表达式时,我们不做任何推断,a 参数的类型为unknown

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-03
    • 1970-01-01
    • 2022-08-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多