在React with material-ui中我试图创建一个接受泛型参数的JSX组件,并使用withStyles
HOC来注入我的样式。
第一种方法是这样的:
const styles = (theme: Theme) => createStyles({
card: {
...
}
});
interface Props<T> {
prop: keyof T,
...
}
type PropsWithStyles<T> = Props<T> & WithStyles<typeof styles>;
export default withStyles(styles)(
class BaseFormCard<T> extends React.Component<PropsWithStyles<T>> {
...
}
),
但是当试图使用它时,泛型类型就会丢失
<BaseFormCard<MyClass> prop={ /* no typings here */ } />
我能找到的唯一解决方案是将导出包装在一个函数中,该函数接受泛型参数并构造组件。
export default function WrappedBaseFormCard<T>(props: Props<T>): ReactElement<Props<T>> {
const wrapper = withStyles(styles)(
class BaseFormCard<T> extends React.Component<PropsWithStyles<T>> {
...
}
) as any;
return React.createElement(wrapper, props);
}
然而,这是非常复杂的,甚至还带有运行时成本,尽管它只是试图解决问题类型。
必须有一种更好的方法来将JSX组件与通用参数和HOC一起使用。
这与https://github.com/mui-org/material-ui/issues/11921的问题密切相关,但从来没有令人满意的解决方案,现在问题已经结束。
我越想到这个问题,我越喜欢Frank Li's approach。我做了两个修改:(1)引入一个额外的SFC以避免强制转换,以及(2)从包裹的组件C
中获取外部道具类型而不是硬编码。 (如果我们对Props<T>
进行硬编码,TypeScript至少会检查它是否与this.C
兼容,但是我们有可能需要this.C
实际上不需要或不能接受this.C
实际接受的可选道具的道具。)它是下颚 - 从extends
子句中的类型参数中删除引用属性类型的工作,但似乎!
class WrappedBaseFormCard<T> extends React.Component<
// Or `PropsOf<WrappedBaseFormCard<T>["C"]>` from @material-ui/core if you don't mind the dependency.
WrappedBaseFormCard<T>["C"] extends React.ComponentType<infer P> ? P : never,
{}> {
private readonly C = withStyles(styles)(
// JSX.LibraryManagedAttributes handles defaultProps, etc. If you don't
// need that, you can use `BaseFormCard<T>["props"]` or hard-code the props type.
(props: JSX.LibraryManagedAttributes<typeof BaseFormCard, BaseFormCard<T>["props"]>) =>
<BaseFormCard<T> {...props} />);
render() {
return <this.C {...this.props} />;
}
}
我认为任何关于这种方法的运行时开销的抱怨在整个React应用程序的上下文中可能都是无意义的;当有人提供支持他们的数据时,我会相信他们。
请注意,Lukas Zech使用SFC的方法是非常不同的:每次外部SFC的道具改变并再次调用时,再次调用withStyles
,生成一个看起来像一个全新的组件类型的React的wrapper
,所以React扔掉了旧的wrapper
实例和一个新的内部BaseFormCard
组件被创建。这会产生不良行为(重置状态),更不用说更大的运行时开销。 (我实际上没有测试过这个,所以如果我错过了什么,请告诉我。)
这是一个不创建包装器组件的解决方案。相反,它创建一个类似于没有'classes'属性的组件类型的类型。
// TypeUtils.tsx
// from: https://stackoverflow.com/questions/48215950/exclude-property-from-type
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
使用此帮助程序类型,您可以创建所需的类型并将样式化组件转换为该类型
type FixedCheckBoxType = <T>(props: Omit<FormCheckBoxProps<T>, 'classes'>) => JSX.Element;
export const FormCheckBox = withStyles(checkboxStyles)(UnstyledFormCheckBox) as FixedCheckBoxType;
可能有更好的方法来做到这一点,但理想情况下,这将由material-ui本身自动完成。
对于每种可能的类型参数,需要有withStyles
类型的重载