【译】TypeScript中的React高阶组件

原文链接:
https://medium.com/@jrwebdev/…

高阶组件(HOCs)在React中是组件复用的一个壮大东西。然则,常常有开发者在连系TypeScript运用中埋怨道很难去为其设置types。

这边文章将会假定你已具有了HOCs的基础知识,并会依据由浅入深的例子来向你展现怎样去为其设置types。在本文中,高阶组件将会被分为两种基础情势,我们将其命名为enhancersinjectors

  • enhancers:用附加的功用/props来包裹组件。
  • injectors:向组件注入props。

请注重,本文中的示例并非最好实践,本文重要只是展现怎样在HOCs中设置types。

Enhancers

我们将从enhancers最先,因为它更轻易去设置types。此情势的一个基础示例是一个向组件增添loading props的HOC,而且将其设置为true的时刻展现loading图。下面是一个没有types的示例:

const withLoading = Component =>
  class WithLoading extends React.Component {
    render() {
      const { loading, ...props } = this.props;
      return loading ? <LoadingSpinner /> : <Component {...props} />;
    }
  };

然后是加上types

interface WithLoadingProps {
  loading: boolean;
}

const withLoading = <P extends object>(Component: React.ComponentType<P>) =>
  class WithLoading extends React.Component<P & WithLoadingProps> {
    render() {
      const { loading, ...props } = this.props;
      return loading ? <LoadingSpinner /> : <Component {...props as P} />;
    }
  };

这里发生了一些事变,所以我们将把它剖析:

interface WithLoadingProps {
  loading: boolean;
}

在这里,声明一个props的interface,将会被增添到被包裹的组件上。

<P extends object>(Component: React.ComponentType<P>)

这里我们运用泛型:P示意通报到HOC的组件的props。React.ComponentType<P>React.FunctionComponent<P> | React.ClassComponent<P>的别号,示意通报到HOC的组件可所以类组件或许是函数组件。

class WithLoading extends React.Component<P & WithLoadingProps>

在这里,我们定义从HOC返回的组件,并指定该组件将包括传入组件的props(P)和HOC的props(WithLoadingProps)。它们经由过程 & 组合在一起。

const { loading, ...props } = this.props;

末了,我们运用loading props有条件地显现加loading图或通报了本身props的组件:

return loading ? <LoadingSpinner /> : <Component {...props as P} />;

注重:因为typescript中能够存在的bug,因而从typescript v3.2最先,这里须要范例转换(props as p)。

我们的withloading HOC也能够重写以返回函数组件而不是类:

const withLoading = <P extends object>(
  Component: React.ComponentType<P>
): React.FC<P & WithLoadingProps> => ({
  loading,
  ...props
}: WithLoadingProps) =>
  loading ? <LoadingSpinner /> : <Component {...props as P} />;

这里,我们对对象rest/spread也有一样的题目,因而经由过程设置显式的返回范例React.FC<P & WithLoadingProps>来处理这个题目,但只能在无状况功用组件中运用WithLoadingProps。

注重:React.FC是React.FunctionComponent的缩写。在初期版本的@types/react中,是React.SFC或React.StatelessFunctionalComponent。

Injectors

injectors是更罕见的HOC情势,但更难为其设置范例。除了向组件中注入props外,在大多数情况下,当包裹好后,它们也会移除注入的props,如许它们就不能再从外部设置了。react redux的connect就是是injector HOC的一个例子,然则在本文中,我们将运用一个更简朴的例子,它注入一个计数器值并回调以增添和削减该值:

import { Subtract } from 'utility-types';

export interface InjectedCounterProps {
  value: number;
  onIncrement(): void;
  onDecrement(): void;
}

interface MakeCounterState {
  value: number;
}

const makeCounter = <P extends InjectedCounterProps>(
  Component: React.ComponentType<P>
) =>
  class MakeCounter extends React.Component<
    Subtract<P, InjectedCounterProps>,
    MakeCounterState
  > {
    state: MakeCounterState = {
      value: 0,
    };

    increment = () => {
      this.setState(prevState => ({
        value: prevState.value + 1,
      }));
    };

    decrement = () => {
      this.setState(prevState => ({
        value: prevState.value - 1,
      }));
    };

    render() {
      return (
        <Component
          {...this.props as P}
          value={this.state.value}
          onIncrement={this.increment}
          onDecrement={this.decrement}
        />
      );
    }
  };

这里有几个症结区分:

export interface InjectedCounterProps {  
  value: number;  
  onIncrement(): void;  
  onDecrement(): void;
}

我们给将要注入到组件的props声明一个interface,该接口将被导出,以便这些props可由被HOC包裹的组件运用:

import makeCounter, { InjectedCounterProps } from './makeCounter';

interface CounterProps extends InjectedCounterProps {
  style?: React.CSSProperties;
}

const Counter = (props: CounterProps) => (
  <div style={props.style}>
    <button onClick={props.onDecrement}> - </button>
    {props.value}
    <button onClick={props.onIncrement}> + </button>
  </div>
);

export default makeCounter(Counter);
<P extends InjectedCounterProps>(Component: React.ComponentType<P>)

我们再次运用泛型,然则此次,你要确保传入到HOC的组件包括注入到个中的props,不然,你将收到一个编译毛病。

class MakeCounter extends React.Component<
  Subtract<P, InjectedCounterProps>,    
  MakeCounterState  
>

HOC返回的组件运用Piotrek Witek’s的utility-types包中的subtract,它将从传入组件的props中减去注入的props,这意味着假如它们设置在天生的包裹组件上,则会收到编译毛病:
![TypeScript compilation error when attempting to set value on the wrapped component
](https://cdn-images-1.medium.c…*xTKe3DWJdC7nAVQnM4bvbg.png)

Enhance + Inject

连系这两种情势,我们将在计数器示例的基础上,许可将最小和最大计数器值通报给HOC,而HOC又被它截取并运用,而不将它们通报给组件:

export interface InjectedCounterProps {
  value: number;
  onIncrement(): void;
  onDecrement(): void;
}

interface MakeCounterProps {
  minValue?: number;
  maxValue?: number;
}

interface MakeCounterState {
  value: number;
}

const makeCounter = <P extends InjectedCounterProps>(
  Component: React.ComponentType<P>
) =>
  class MakeCounter extends React.Component<
    Subtract<P, InjectedCounterProps> & MakeCounterProps,
    MakeCounterState
  > {
    state: MakeCounterState = {
      value: 0,
    };

    increment = () => {
      this.setState(prevState => ({
        value:
          prevState.value === this.props.maxValue
            ? prevState.value
            : prevState.value + 1,
      }));
    };

    decrement = () => {
      this.setState(prevState => ({
        value:
          prevState.value === this.props.minValue
            ? prevState.value
            : prevState.value - 1,
      }));
    };

    render() {
      const { minValue, maxValue, ...props } = this.props;
      return (
        <Component
          {...props as P}
          value={this.state.value}
          onIncrement={this.increment}
          onDecrement={this.decrement}
        />
      );
    }
  };

这里,Subtract与types交集相连系,将组件本身的props与HOCs本身的props相连系,减去注入组件的props:

Subtract<P, InjectedCounterProps> & MakeCounterProps

除此之外,与其他两种情势比拟,没有真正的差别须要强调,然则这个示例确切带来了一些高阶组件的题目。这些并非真正特定于typescript的,但值得细致申明,以便我们能够议论怎样运用typescript来处理这些题目。

起首,MinValue和MaxValue被HOC阻拦,而不是通报给组件。然则,你或许愿望它们是如许的,如许你就能够基于这些值禁用递增/递减按钮,或许向用户显现一条音讯。假如用HOC,你也能够简朴地修正它来注入这些值,然则假如你没有(比方,它来自一个NPM包),这就将会是一个题目。

其次,由HOC注入的prop有一个异常通用的称号;假如要将其用于其他目标,或许假如要从多个HOC注入prop,则此称号能够与其他注入的prop争执。您能够将称号更改成不太通用的处理方案,但就处理方案而言,这不是一个很好的处理方案!

    原文作者:玩弄心里的鬼
    原文地址: https://segmentfault.com/a/1190000018906665
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞