TypeScript中的React Render Props

更新日期: 2019-04-18阅读: 2.7k标签: props

和之前的文章一样,本文也要求你对render props有一些知识背景,如果没有官方文档可能会对你有很大的帮助。本文将会使用函数作为children的render props模式以及结合react的context api来作为例子。如果你想使用类似于render这样子的render props,那也只需要把下面例子的children作为你要渲染的props即可。

为了展示render props,我们将要重写之前文章的makeCounter 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}
        />
      );
    }
  };

HOC向组件注入了value和两个回调函数(onIncrement 和 onDecrement),此外还在HOC内部使用minValue和maxValue两个props而没有传递给组件。我们讨论了如果组件需要知道这些值,如何不传递props可能会出现问题,并且如果使用多个HOC包装组件,注入的props的命名也可能与其他HOC注入的props冲突。

makeCounter HOC将会被像下面这样重写:

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

interface MakeCounterProps {
  minValue?: number;
  maxValue?: number;
  children(props: InjectedCounterProps): JSX.Element;
}

interface MakeCounterState {
  value: number;
}

class MakeCounter extends React.Component<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() {
    return this.props.children({
      value: this.state.value,
      onIncrement: this.increment,
      onDecrement: this.decrement,
    });
  }
}

这里有一些需要注意的变化。首先,injectedCounterProps被保留,因为我们需要定义一个props的interface在render props函数调用上而不是传递给组件的props(和HOC一样)。MakeCounter(MakeCounterProps)的props已经改变,加上以下内容:

children(props: InjectedCounterProps): JSX.Element;

这是render prop,然后组件内需要一个函数带上注入的props并返回JSX element。下面是它用来突出显示这一点的示例:

interface CounterProps {
  style: React.cssProperties;
  minValue?: number;
  maxValue?: number;
}

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

MakeCounter自己的组件声明变得简单多了;它不再被包装在函数中,因为它不再是临时的,输入也更加简单,不需要泛型、做差值和类型的交集。它只有简单的MakeCounterProps和MakeCounterState,就像其他任何组成部分一样:

class MakeCounter extends React.Component<
  MakeCounterProps, 
  MakeCounterState
>

最后,render()的工作也变少了;它只是一个函数调用并带上注入的props-不需要破坏和对象的props扩展运算符展开了!

return this.props.children({
  value: this.state.value,
  onIncrement: this.increment,
  onDecrement: this.decrement,
});

然后,render prop组件允许对props的命名和在使用的灵活性上进行更多的控制,这是和HOC等效的一个问题:

interface CounterProps {
  style: React.CSSProperties;
  value: number;
  minCounterValue?: number;
  maxCounterValue?: number;
}

const Counter = (props: CounterProps) => (
  <MakeCounter
    minValue={props.minCounterValue}
    maxValue={props.maxCounterValue}
  >
    {injectedProps => (
      <div>
        <div>Some other value: {props.value}</div>
        <div style={props.style}>
          <button onClick={injectedProps.onDecrement}> - </button>
          {injectedProps.value}
          <button onClick={injectedProps.onIncrement}> + </button>
        </div>
        {props.minCounterValue !== undefined ? (
          <div>Min value: {props.minCounterValue}</div>
        ) : null}
        {props.maxCounterValue !== undefined ? (
          <div>Max value: {props.maxCounterValue}</div>
        ) : null}
      </div>
    )}
  </MakeCounter>
);

有了所有这些好处,特别是更简单的输入,那么为什么不一直使用render props呢?当然可以,这样做不会有任何问题,但要注意render props组件的一些问题。

首先,这里有一个关注点以外的问题;MakeCounter组件现在被放在了Counter组件内而不是包装了它,这使得隔离测试这两个组件更加困难。其次,由于props被注入到组件的渲染函数中,因此不能在生命周期方法中使用它们(前提是计数器被更改为类组件)。

这两个问题都很容易解决,因为您可以使用render props组件简单地生成一个新组件:

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

interface WrappedCounterProps extends CounterProps {
  minValue?: number;
  maxValue?: number;
}

const WrappedCounter = ({
  minValue,
  maxValue,
  ...props
}: WrappedCounterProps) => (
  <MakeCounter minValue={minValue} maxValue={maxValue}>
    {injectedProps => <Counter {...props} {...injectedProps} />}
  </MakeCounter>
);

另一个问题是,一般来说,它不太方便,现在使用者需要编写很多样板文件,特别是如果他们只想将组件包装在一个单独的临时文件中并按原样使用props。这可以通过从render props组件生成HOC来补救:

import { Subtract, Omit } from 'utility-types';
import MakeCounter, { MakeCounterProps, InjectedCounterProps } from './MakeCounter';

type MakeCounterHocProps = Omit<MakeCounterProps, 'children'>;

const makeCounter = <P extends InjectedCounterProps>(
  Component: React.ComponentType<P>
): React.SFC<Subtract<P, InjectedCounterProps> & MakeCounterHocProps> => ({
  minValue,
  maxValue,
  ...props
}: MakeCounterHocProps) => (
  <MakeCounter minValue={minValue} maxValue={maxValue}>
    {injectedProps => <Component {...props as P} {...injectedProps} />}
  </MakeCounter>
);

在这里,上一篇文章的技术,以及render props组件的现有类型,被用来生成HOC。这里唯一需要注意的是,我们必须从HOC的props中移除render prop(children),以便在使用时不暴露它:

type MakeCounterHocProps = Omit<MakeCounterProps, 'children'>;


最后,HOC和render props组件之间的权衡归结为灵活性和便利性。这可以通过首先编写render props组件,然后从中生成HOC来解决,这使使用者能够在两者之间进行选择。这种方法在可重用组件库中越来越常见,例如优秀的render-fns库。

就TypeScript而言,毫无疑问,hocs的类型定义要困难得多;尽管通过这两篇文章中的示例,它表明这种负担是由HOC的提供者而不是使用者承担的。在使用方面,可以认为使用HOC比使用render props组件更容易。

在react v16.8.0之前,我建议使用render props组件以提高键入的灵活性和简单性,如果需要,例如构建可重用的组件库,或者对于简单在项目中使用的render props组件,我将仅从中生成HOC。在react v16.8.0中释放react hook之后,我强烈建议在可能的情况下对两个高阶组件或render props使用它们,因为它们的类型更简单。


原文链接:https://medium.com/@jrwebdev/react-render-props-in-typescript-b561b00bc67c


链接: https://fly63.com/article/detial/2936

Node.js之react.js组件-Props应用

render props是指一种在 React 组件之间使用一个值为函数的 prop 共享代码(个人理解:将组件进行函数化,通过调用组件名实现,组件的利用,以元素的形式调用,并渲染画面),具有 render prop 的组件接受一个函数

React组件设计模式-Render-props

写业务时,我们经常需要抽象一些使用频率较高的逻辑,但是除了高阶组件可以抽象逻辑,RenderProps也是一种比较好的方法。RenderProps,顾名思义就是将组件的props渲染出来。实际上是让组件的props接收函数,由函数来渲染内容。将通用的逻辑

props、属性、事件传递

之前看了一篇关于Vue开发技巧的文章,其中提到了在写高级组件时,通过v-bind=$props将props一次性向下传递。这种向下传递的方式我之前没有用过,便看了下官网的介绍,并补充了一些相关API用法,在这里记录一下,方便自己以后查看

props,挂载函数和快照测试

我们测试了通过一些 props 的结果。但是实际上,我们可以直接测试 props。让我们回到上次的 ToDoList组件,不过这次要用一个新的 Task 组件。我们将要测试 ToDoList 组件是否渲染 Task 组件,并将任务名称传递给他们。

vue props传值常见问题

传入的值想作为局部变量来使用,直接使用会 报错。错误是说的避免直接修改父组件传入的值,因为会改变父组件的值,解决方案:可以在data中重新定义一个变量,改变指向,但是也只是针对简单数据类型

Vue 中的 Props 与 Data 细微差别,你知道吗?

Vue提供了两种不同的存储变量:props和data。这些方法一开始可能会让人感到困惑,因为它们做的事情很相似,而且也不清楚什何时使用props,何时使用data。那么props和data有什么区别呢?

React组件的state和props

React 的数据是自顶向下单向流动的,即从父组件到子组件中,组件的数据存储在 props 和 state 中。实际上在任何应用中,数据都是必不可少的,我们需要直接的改变页面上一块的区域来使得视图的刷新

验证 Vue Props 类型,这几种方式你可能还没试用过!

vue 要求任何传递给组件的数据,都要声明为 props。此外,它还提供了一个强大的内置机制来验证这些数据。这就像组件和消费者之间的契约一样,确保组件按预期使用。

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!