Typescript with React - use HOC on a generic component class

自作多情 提交于 2019-11-29 15:13:09

问题


I have a generic React component, say like this one:

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

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

I also have a HOC that looks similar to this one (but is less pointless):

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

But when I use a component like this:

const FooWithTd = withTd(Foo);

There is no way to pass the type argument, as you can do neither withTd(Foo<T>), nor can you do FooWithTd, the type is always wrong. What is the proper way to do that?

EDIT: The problem is that I want to be able to have something like <FooWithTd<number> {...someprops}/> later on, as I don't know the desired type for T in the HOC.


回答1:


Thanks for asking this question. I just figured out a way to specify a type parameter to a component after wrapping it with an HOC and I thought I'd share.

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>`

The main idea is to cast the wrapped component as a function that takes in a type parameter (e.g. T) and use that type parameter to derive the external props after the component has been wrapped.

If you do this, then you can specify a type parameter when using the wrapped version of GenericComponent e.g.:

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

Hopefully the code is explanatory enough for those who still have this problem. In general though, I consider this relatively advanced typescript usage and it's probably easier to use any instead of a generic parameter in the props




回答2:


You can wrap your component which is created from a HOC into another component. It would look something like this:

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} />;
     }
}

Remember, you probably don't want the HOC inside your render function because it means that the component will be recreated every each render.




回答3:


EDIT: After some changes to your code, it was only a wrong constraint T in your withTd function.

// 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)

No longer relevant after EDIT :

You may find more information at this issue https://github.com/Microsoft/TypeScript/issues/3960




回答4:


Just stumbled upon this as well and thought I'd share what I came up with in the end.

Building on what @rico-kahler provided, my approach mapped to your code would be

export const FooWithTd = withTd(Foo) as <T>(props: FooProps<T>) => React.ReactElement<FooProps<T>>;

which you can then use like this

export class Bar extends React.Component<{}> {
  public render() {
    return (
      <FooWithTd<number> />
    );
  }
}

In my case, I have defaultProps as well and I inject props by ways of another HOC, the more complete solution would look like this:

type DefaultProps = "a" | "b";
type InjectedProps = "classes" | "theme";
type WithTdProps<T> = Omit<FooProps<T>, DefaultProps | InjectedProps> & Partial<FooProps<T> & { children: React.ReactNode }>;
export const FooWithTd = withTd(Foo) as <T>(props: WithTdProps<T>) => React.ReactElement<WithTdProps<T>>;


来源:https://stackoverflow.com/questions/51984093/typescript-with-react-use-hoc-on-a-generic-component-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!