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

Posted

技术标签:

【中文标题】TypeScript React HOC 返回类型与装饰器兼容【英文标题】:TypeScript React HOC return type compatible with decorator 【发布时间】:2019-04-30 04:13:50 【问题描述】:

我正在使用带有 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&lt;any&gt;,但唯一有效的只有 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】:

第一个问题是P 不在可以推断的位置。因为它在withBadge 上,并且该调用不会包含它。

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

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

这意味着装饰器签名不能是(WrappedComponent: ComponentClass&lt;P&gt;) =&gt; ReactNode,因为它返回ReactNode。它甚至不能是(WrappedComponent: ComponentClass&lt;P&gt;) =&gt; ComponentClass&lt;P&gt;,因为它不会返回与传入的完全相同的值。一个解决方案是对编译器撒谎,并在我们实际返回一个新类时声明我们返回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;

【讨论】:

你可以在 BadgedCart 类中添加一个函数,会报错。

以上是关于TypeScript React HOC 返回类型与装饰器兼容的主要内容,如果未能解决你的问题,请参考以下文章

带有 React.createContext 的 Typescript HOC

如何将儿童类型传递给 HOC 反应的道具

如何在 TypeScript 中为 React Apollo Query 组件编写 HOC?

React TypeScript HoC - 将组件作为道具传递

Typescript with React - 在通用组件类上使用 HOC

Typescript 无状态 HOC react-redux 注入道具打字