带有 React 的 Typescript - 在通用组件类上使用 HOC

IT技术 reactjs typescript generics higher-order-components
2021-05-07 04:43:15

我有一个通用的 React 组件,像这样说:

class Foo<T> extends React.Component<FooProps<T>, FooState> {
    constructor(props: FooProps<T>) {
        super(props);

    render() {
        return <p> The result is {SomeGenericFunction<T>()}</p>;
    }
}

我还有一个 HOC,它看起来与这个相似(但没那么无意义):

export const withTd = 
    <T extends WithTdProps>(TableElement: React.ComponentType<T>): React.SFC<T> => 
(props: T) => <td><TableElement {...props}/></td>;

但是当我使用这样的组件时:

const FooWithTd = withTd(Foo);

没有办法传递类型参数,因为你既不能做withTd(Foo<T>),也不能做FooWithTd,类型总是错误的。这样做的正确方法是什么?

编辑:问题是我希望能够在<FooWithTd<number> {...someprops}/>以后使用类似的东西,因为我不知道THOC 中所需的类型

4个回答

您可以将从 HOC 创建的组件包装到另一个组件中。它看起来像这样:

class FooWithTd<T> extends React.Component<SomeType<T>> {
     private Container: React.Component<SomeType<T> & HOCResultType>; 

     constructor(props:SomeType<T>){
          super(props);
          this.Container = withTd(Foo<T>);
     }

     render() {
          return <this.Container {...this.props} />;
     }
}

请记住,您可能不希望在渲染函数中使用 HOC,因为这意味着每次渲染都会重新创建组件。

感谢您提出这个问题。在用 HOC 包装组件后,我想出了一种为组件指定类型参数的方法,我想我会分享。

import React from 'react';
import withStyles from '@material-ui/core/styles/withStyles';
import { RemoveProps } from '../helpers/typings';

const styles = {
  // blah
};

interface Props<T> {
  classes: any;
  items: T[];
  getDisplayName: (t: T) => string;
  getKey: (t: T) => string;
  renderItem: (t: T) => React.ReactNode;
}

class GenericComponent<T> extends React.Component<Props<T>, State> {
  render() {
    const { classes, items, getKey, getDisplayName, renderItem } = this.props;

    return (
      <div className={classes.root}>
        {items.map(item => (
          <div className={classes.item} key={getKey(item)}>
            <div>{getDisplayName(item)}</div>
            <div>{renderItem(item)}</div>
          </div>
        ))}
      </div>
    );
  }
}

//   👇 create a `type` helper to that output the external props _after_ wrapping it
type ExternalProps<T> = RemoveProps<Props<T>, 'classes'>;
export default withStyles(
  styles
)(GenericComponent) as <T extends any>(props: ExternalProps<T>) => any;
//                       👆 cast the wrapped component as a function that takes
//                          in a type parameter so we can use that type
//                          parameter in `ExternalProps<T>`

主要思想是将包装的组件转换为一个接受类型参数(例如T的函数,并在组件被包装使用该类型参数来派生外部props

如果你这样做,那么你可以在使用GenericComponenteg的包装版本时指定一个类型参数

<GenericComponent<string> {/*...*/} />

希望代码对那些仍然有这个问题的人有足够的解释。但总的来说,我认为这种相对高级的typescript用法可能更容易使用,any而不是props中的通用参数

编辑:对您的代码进行一些更改后,这只是您的 withTd 函数中的错误约束 T。

// I needed to change the constraint on T, but you may adapt with your own needs
export const withTd = <T extends FooProps<WithTdProps>>(
  TableElement: React.ComponentType<T>
): React.SFC<T> => (props: T) => (
  <td>
    <TableElement {...props} />
  </td>
)

// Explicitly typed constructor
// Removed after EDIT
//const FooW = Foo as new (props: FooProps<WithTdProps>) => Foo<WithTdProps>

// Inferred as React.StatelessComponent<FooProps<WithTdProps>>
const FooWithTd = withTd(Foo)

编辑后不再相关:

您可以在此问题上找到更多信息https://github.com/Microsoft/TypeScript/issues/3960

解决方法:简单案例

如果您的组件的类型参数仅用于将其传递给 props,并且组件的用户不希望它具有任何功能,而不仅仅是传递 props 和渲染,您可以明确地将您的结果硬转换hoc(...args)(Component)为 React 的功能组件类型,例如这:

import React, {ReactElement} from 'react';

class MyComponent<T> extends React.Component<MyProps<T>> { /*...*/ }

const kindaFixed = myHoc(...args)(MyComponent) as unknown as <T>(props: MyProps<T>) => ReactElement;

解决方法:更复杂,并且有一些运行时成本

您可以在这里使用类似结构的功能

class MyComponent<T> extends React.Component<MyProps<T>> { /*...*/ }

export default function MyComponentFabric<T>() {
    return hoc(...args)(MyComponent as new(props: MyProps<T>) => MyComponent<T>);
}

这将要求您为使用它的每种类型创建新版本的包装组件:

import MyComponentFabric from '...whenever';

const MyComponentSpecificToStrings = MyComponentFabric<string>();

它将允许您访问组件的所有公共实例字段和方法。

概括

当试图利用我面临这个问题connectreact-redux我的ExampleGenericComponent<T>不幸的是,在 TypeScript 支持 HKT 之前,它无法正确修复,并且您使用的任何 HOC 都会根据此功能更新其类型。

当您需要访问组件实例字段和方法时,除了渲染之外,可能没有正确的解决方案(至少现在是这样)。“正确”是指“没有丑陋的显式类型转换”和“没有运行时成本”。

您可以尝试的一件事是将您的类组件拆分为两个组件,一个将与 HOC 一起使用,另一个将提供您需要的字段和方法。