TypeScript参数类型推断失败

时间:2018-07-05 04:02:49

标签: typescript types parameters conditional type-inference

我为此创建了一个bug report,但是也许有人对解决方法有所了解。基本上我想根据另一个函数的类型有一个函数的参数:

declare function foo<P>(params: { f: (p: P) => void } & P): void;

foo({
  f(params: { bar(x: number): void }) {},
  bar(x) {},
});

因此,有一个特殊的参数f,它具有函数类型,并且函数期望的任何属性都必须作为foo的参数包括在内。这里再次将bar的参数推断为any,但应为number

2 个答案:

答案 0 :(得分:4)

每当我看到需要具有依赖于参数值的函数类型的要求时,首先想到的是“使用重载”:

declare function foo(params: { tag: 'a', bar(x: number): void }): void;
declare function foo(params: { tag: 'b', bar(x: boolean): void }): void;

foo({ tag: 'a', bar(x) { console.log('x =', x)} }); // (parameter) x: number

更新我假设在以下代码中foof是React组件,而foo公开了f作为其属性,如下以及f具有的所有属性。

declare function foo<P>(params: { f: (p: P) => void } & P): void;

foo({
  f(params: { bar(x: number): void }) {},
  bar(x) {},
});

有一种方法可以重写它,只要您不坚持f的名称(这是foo的属性)以及f属性的类型调用时必须在foo参数内声明为内联。

您可以声明一个描述此类组合组件属性的类型,它具有两个通用参数:内部组件属性的类型和外部属性中内部组件的名称:

type ComposedProps<InnerProps, N extends string> = 
       InnerProps & { [n in N]: (props: InnerProps) => void };  

// Also I assume in reality it does not return `void` but `React.Element`,
//  but I don't think it matters

declare function foo<P>(params: P): void;

// foo generic parameter is given explicitly,
//  which allows to infer (and not repeat) parameter types for `f` and `bar` 
foo<ComposedProps<{bar(x: number): void}, 'f'>>({
  f(params) {}, // params: { bar(x: number): void; }
  bar(x) {}, // (parameter) x: number
});

答案 1 :(得分:2)

正如我们在评论中讨论的,该场景实际上与反应组件以及新的通用组件功能与默认通用参数交互的方式有关。

问题

如果我们有一个具有泛型参数的react组件,并且该泛型参数的默认值具有function字段,则不会推断我们传入的函数的参数。一个简单的例子是:

)

可能的解决方案

让我在此做一个警告,我不知道它在所有情况下都行得通,还是不知道这取决于某些会改变的编译器行为。我的发现是,这是一个非常脆弱的解决方案,即使可以合理地认为是等效的并且无法使用,也可以更改任何细节。我可以说的是,正如所介绍的那样,它可以对我进行测试。

解决方案是利用以下事实:属性类型是从构造函数的返回值中获取的,如果我们对默认泛型参数进行了一些简化,则会得到我们想要的推断。唯一的问题是检测到我们正在处理默认的通用参数。为此,我们可以在默认参数中添加一个额外的字段,然后使用条件类型对其进行测试。请注意,在可以根据使用的属性推论 class Foo<P = { fn : (e: string)=> void }> extends React.Component<P>{} let foo = () => ( <div> <Foo fn={e=> e}/> {/* e is any */} </div> ) 的简单情况下,这是行不通的,因此我们有一个示例,其中该组件从另一个组件提取属性(实际上更接近于您的用例) :

P

应用于材料用户界面

我在material-ui回购中尝试过的一个示例是// Do not change { __default?:true } to DefaultGenericParameter it only works with the former for some reason type IfDefaultParameter<C, Default, NonDefault> = C extends { __default?:true } ? Default : NonDefault type DefaultGenericParameter = { __default? : true; } export type InferredProps<C extends React.ReactType> = C extends React.ComponentType<infer P> ? P : C extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[C] : {}; // The props for Foo, will extract props from the generic parameter passed in type FooProps<T extends React.ReactType = React.ComponentClass<{ fn: (s: string )=> void }>> = InferredProps<T> & { other?: string } // Actual component class class _Foo<P> extends React.Component<P>{} // The public facing constructor const Foo : { new <P extends React.ReactType & {__default?: true} = React.ComponentClass<{ fn: (s: string )=> void }> & DefaultGenericParameter>(p: P) : IfDefaultParameter<P, _Foo<FooProps>, _Foo<FooProps<P>>> } = _Foo as any; let foo = () => ( <div> <Foo fn={e=> e}/> {/* e is string */} <Foo<React.ComponentClass<{ fn: (s: number )=> void }>> fn={e=> e}/> {/* e is number */} </div> ) 组件,它似乎可以工作:

Avatar

用法

export type AvatarProps<C extends AnyComponent = AnyComponent> = StandardProps<
  PassthruProps<C, 'div'>,
  AvatarClassKey
> & {
  alt?: string;
  childrenClassName?: string;
  component?: C;
  imgProps?: React.HtmlHTMLAttributes<HTMLImageElement>;
  sizes?: string;
  src?: string;
  srcSet?: string;
};

type IfDefaultParameter<C, Default, NonDefault> = C extends { __default?:true } ? Default : NonDefault
type DefaultGenericParameter = { __default? : true; }

declare const Avatar : {
  new <C extends AnyComponent & DefaultGenericParameter = 'div' & DefaultGenericParameter>(props: C) 
  : IfDefaultParameter<C, React.Component<AvatarProps<'div'>>, React.Component<AvatarProps<C>>>
}

祝您好运,如果有帮助,请告诉我。这是一个有趣的小问题,已经让我醒了十天了:)