带有withStyles的JSX元素中的泛型类型参数

问题描述 投票:3回答:3

在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的问题密切相关,但从来没有令人满意的解决方案,现在问题已经结束。

reactjs typescript material-ui
3个回答
3
投票

我越想到这个问题,我越喜欢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组件被创建。这会产生不良行为(重置状态),更不用说更大的运行时开销。 (我实际上没有测试过这个,所以如果我错过了什么,请告诉我。)


1
投票

这是一个不创建包装器组件的解决方案。相反,它创建一个类似于没有'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本身自动完成。


0
投票

对于每种可能的类型参数,需要有withStyles类型的重载

© www.soinside.com 2019 - 2024. All rights reserved.