在React with material-ui中,我试图创建一个JSX组件,该组件接受通用参数,并且还使用withStylesHOC注入我的样式。
withStyles
第一种方法是这样的:
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); }
尽管这只是在试图解决类型问题,但是这却非常复杂,甚至带来运行时成本。
必须有一种更好的方法来使用具有通用参数和HOC的JSX组件。
这与https://github.com/mui-org/material- ui/issues/11921上的问题密切相关,但是从来没有令人满意的解决方案,现在问题已解决。
我对问题的思考越多,我就越喜欢Frank Li的方法。我将做两个修改:(1)引入一个额外的SFC以避免C强制转换;(2)从包装的组件中获取外部props类型,而不是对其进行硬编码。(如果我们硬编码Props<T>,打字稿至少会检查它是 兼容 与this.C,但我们在要求道具的风险this.C实际上并不需要或没有接受可选道具是this.C真正接受。)它的令人瞠目结舌的是引用从extends子句中的类型实参的属性类型起作用,但是似乎可以!
C
Props<T>
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都会再次调用它,生成wrapper看起来像React的全新组件类型,因此React会丢弃旧的wrapper实例,并BaseFormCard创建一个新的内部组件。这将具有不良的 行为 (重置状态),更不用说更大的运行时开销了。(我尚未实际测试过,因此,如果我缺少某些东西,请告诉我。)
wrapper
BaseFormCard