正如我们在评论中讨论的那样,这种情况实际上与React组件有关,以及新的通用组件功能如何与默认的通用参数交互。
问题
如果我们有一个带有通用参数的React组件,并且通用参数的默认值具有函数字段,则我们传递给函数的参数将无法推断。一个简单的例子是:
class Foo<P = { fn : (e: string)=> void }> extends React.Component<P>{}
let foo = () => (
<div>
<Foo fn={e=> e}/> {/* e is any */}
</div>
)
可能的解决方案
首先声明一下,我不知道这个方法在所有情况下是否适用,或者它是否依赖于某些编译器行为的更改。但我发现这是一种非常脆弱的解决方案,任何一个细节变化,即使是某些被认为是等价的内容,都会导致它无法工作。但根据我的测试,就目前呈现出的情况而言,它可以正常工作。
这个解决方案是利用属性类型来自构造函数返回值的事实,如果我们将默认泛型参数简化一点,就可以得到我们想要的类型推断。唯一的问题是如何检测到我们正在处理默认泛型参数。我们可以通过为默认参数添加一个额外的字段,并使用条件类型进行测试来解决这个问题。还有一个需要注意的地方,这对于可以基于已使用属性推断P的简单情况不起作用,因此我们将提供一个从另一个组件中提取属性的例子(实际上更接近于您的用例):
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] :
{};
type FooProps<T extends React.ReactType = React.ComponentClass<{ fn: (s: string )=> void }>> = InferredProps<T> & {
other?: string
}
class _Foo<P> extends React.Component<P>{}
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
我在 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天 :)
this
类型的事情我们可以做。例如:`function foo<T>(foo: T & ThisType<T & { default : number }>) { }foo({ customValue: 10, method() { this.customValue + this.default } })`但是没有一个最小的工作示例,我无法确定这是否有帮助。 - Titian Cernicova-Dragomirfoo
和f
是 React 组件。我们真的希望避免为外部组件提供类型参数,而是让它推断内部组件的类型,从而推断出其他属性的类型。还有一个复杂的问题,即component
属性(例如此示例中的f
)是可选的,并且应该默认为某个具有特定一组附加属性的组件。例如,在Button
的情况下,当未提供component
时,默认为button
,这意味着可以使用所有button
的标准属性。 - Tom Crockett