与装饰器兼容的TypeScript React HOC返回类型

时间:2018-11-28 09:37:17

标签: reactjs typescript typescript-typings higher-order-components typescript-types

我正在使用带有TypeScript的React Native。我已经编写了一个HOC,我想用作装饰器来为组件赋予徽章:

import React, { Component, ComponentClass, ReactNode } from "react";
import { Badge, BadgeProps } from "../Badge";

function withBadge<P>(
  value: number,
  hidden: boolean = value === 0
): (WrappedComponent: ComponentClass<P>) => ReactNode {
  return (WrappedComponent: ComponentClass<P>) =>
    class BadgedComponent extends Component<P> {
      render() {
        return (
          <React.Fragment>
            <WrappedComponent {...this.props} />
            {!hidden && <Badge value={value} />}
          </React.Fragment>
        );
      }
    };
}

export default withBadge;

现在的问题是,当我尝试使用这种组件作为装饰器时:

import React, { PureComponent } from "react";
import { Icon } from "react-native-elements";
import { getIconName } from "../../services/core";
import withBadge from "../WithBadge/withBadge";
import styles from "./styles";

@withBadge(1)
export default class BadgedCart extends PureComponent {
  render() {
    return (
      <Icon
        type="ionicon"
        name={getIconName("cart")}
        containerStyle={styles.iconRight}
        onPress={() => {
          // Nothing.
        }}
      />
    );
  }
}

我得到了错误:

[ts]
Unable to resolve signature of class decorator when called as an expression.
  Type 'null' is not assignable to type 'void | typeof BadgedCart'. [1238]

我尝试了其他不同的返回类型,例如JSX.ElementReactElement<any>,但唯一起作用的只是any,这违背了TypeScript的目的。高阶组件应该具有什么返回类型?

编辑:当我将返回类型(如建议的Praveen更改为typeof PureComponent时,@withBadge(1)的错误更改为:

[ts]
Unable to resolve signature of class decorator when called as an expression.
  Type 'typeof PureComponent' is not assignable to type 'typeof BadgedOrders'.
    Type 'PureComponent<any, any, any>' is not assignable to type 'BadgedOrders'.
      Types of property 'render' are incompatible.
        Type '() => ReactNode' is not assignable to type '() => Element'.
          Type 'ReactNode' is not assignable to type 'Element'.
            Type 'undefined' is not assignable to type 'Element'. [1238]

如果我尝试将其更改为PureComponent

    class BadgedComponent extends Component<P> {
      render() {
        return (
          <React.Fragment>
            <WrappedComponent {...this.props} />
            {!hidden && <Badge value={value} />}
          </React.Fragment>
        );
      }
    };

引发错误:

[ts]
Type '(WrappedComponent: ComponentClass<P, any>) => typeof BadgedComponent' is not assignable to type '(WrappedComponent: ComponentClass<P, any>) => PureComponent<{}, {}, any>'.
  Type 'typeof BadgedComponent' is not assignable to type 'PureComponent<{}, {}, any>'.
    Property 'context' is missing in type 'typeof BadgedComponent'. [2322]

1 个答案:

答案 0 :(得分:4)

第一个问题是P不在可以推断的位置。由于它位于withBadge上,因此该呼叫将不包含它。

第二个问题是类装饰器必须返回void或与输入类相同的类型:

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;

这意味着装饰器签名不能为(WrappedComponent: ComponentClass<P>) => ReactNode,因为它返回ReactNode。它甚至不能为(WrappedComponent: ComponentClass<P>) => ComponentClass<P>,因为它返回的结果与传入的结果不完全相同。一种解决方案是对编译器撒谎,并在我们实际返回a时声明我们返回void新课。这对运行时不会有太大影响:

class Badge extends React.Component<{ value: number }> { 

}

function withBadge(
value: number,
hidden: boolean = value === 0
): <P extends object>(WrappedComponent: ComponentClass<P>) => void {
return <P extends object>(WrappedComponent: ComponentClass<P>) =>
    class BadgedComponent extends Component<P> {
    render() {
        return (
        <React.Fragment>
            <WrappedComponent {...this.props} />
            {!hidden && <Badge value={value} />}
        </React.Fragment>
        );
    }
    };
}

@withBadge(1)
export default class BadgedCart extends PureComponent {
    render() {
        return (
        <div />
        );
    }
}

您可以考虑放弃装饰器方法,转而使用简单的函数调用:

class Badge extends React.Component<{ value: number }> { 

}

function withBadge(
    value: number,
    hidden: boolean = value === 0
): <P extends object>(WrappedComponent: ComponentClass<P>) => ComponentClass<P> {
return <P extends object>(WrappedComponent: ComponentClass<P>) =>
    class BadgedComponent extends Component<P> {
        render() {
            return (
            <React.Fragment>
                <WrappedComponent {...this.props} />
                {!hidden && <Badge value={value} />}
            </React.Fragment>
            );
        }
    };
}

const  BadgedCart = withBadge(1)(class extends PureComponent {
    render() {
        return (
        <div />
        );
    }
});
export default BadgedCart;