正如我们在 cmets 中讨论的那样,该场景实际上与 React 组件以及新的泛型组件功能与默认泛型参数交互的方式有关。
问题
如果我们有一个带有泛型参数的 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 的简单情况,因此我们将有一个示例,其中组件从另一个组件中提取属性(实际上更接近您的用例):
// 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>
)
应用于材质界面
我在 material-ui 存储库中尝试过的一个示例是 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>>>
}
用法
const AvatarTest = () => (
<div>
{/* e is React.MouseEvent<HTMLDivElement>*/}
<Avatar onClick={e => log(e)} alt="Image Alt" src="example.jpg" />
{/* e is {/* e is React.MouseEvent<HTMLButtonElement>*/}*/}
<Avatar<'button'> onClick={e => log(e)} component="button" alt="Image Alt" src="example.jpg" />
<Avatar<React.SFC<{ x: number }>>
component={props => <div>{props.x}</div>} // props is props: {x: number;} & {children?: React.ReactNode;}
x={3} // ok
// IS NOT allowed:
// onClick={e => log(e)}
alt="Image Alt"
src="example.jpg"
/>
</div>
祝你好运,如果有任何帮助,请告诉我。这是一个有趣的小问题,让我已经睡了 10 天了 :)