React RenderProps模式超详细讲解

目录
  • 正文
    • 使用Render Props来完成关注点分离
    • render prop的prop名不一定叫render
  • 注意点

render prop是一个技术概念。它指的是使用值为function类型的prop来实现React component之间的代码共享。

如果一个组件有一个render属性,并且这个render属性的值为一个返回React element的函数,并且在组件内部的渲染逻辑是通过调用这个函数来完成的。那么,我们就说这个组件使用了render props技术。

<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>

不少类库都使用了这种技术,比如说:React Router和Downshift。

在这个文档里面,我们将会讨论为什么render props是如此有用,你该如何编写自己的render props组件。

正文

使用Render Props来完成关注点分离

在React中,组件是代码复用的基本单元(又来了,官方文档不断地在强调这个准则)。到目前为止,在React社区里面,关于共享state或者某些相似的行为(比如说,将一个组件封装进另一拥有相同state的组件)还没有一个明朗的方案。

举个例子,下面这个组件是用于在web应用中追踪鼠标的位置:

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }
  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }
  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        <h1>Move the mouse around!</h1>
        <p>The current mouse position is ({this.state.x}, {this.state.y})</p>
      </div>
    );
  }
}

随着光标在屏幕上面移动,这个组件将会在文档的<p>标签里面显示当前光标在x,y轴上的坐标值。

那么问题来了: 我们该如何在别的组件复用这种行为(指的是监听mouseMove事件,获取光标的坐标值)呢?换句话说,如果别的组件也需要知道目前光标的坐标值,那我们能不能将这种行为封装好,然后在另外一个组件里面开箱即用呢?

因为,在React中,组件是代码复用的基本单元(again)。那好,我们一起来重构一下代码,把我们需要复用的行为封装到<Mouse>组件当中。

// The <Mouse> component encapsulates the behavior we need...
class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }
  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }
  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>

        {/* ...but how do we render something other than a <p>? */}
        <p>The current mouse position is ({this.state.x}, {this.state.y})</p>
      </div>
    );
  }
}
class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse />
      </div>
    );
  }
}

现在,<Mouse>组件看似把所有跟监听mousemove事件,保存光标的坐标值等相关的行为封装在一起了。实际上,它还不能达到真正的可复用。

假设,我们需要实现这么一个组件。它需要渲染出一只用图片表示的猫去追逐光标在屏幕上移动的视觉效果。我们可能会通过向<Cat>组件传递一个叫mouse(它的值为{{x,y}})的prop来获得当前光标所在位置。参考React实战视频讲解:进入学习

首先,我们会在<Mouse>组件的render方法里面插入这个<Cat>组件,像这样子:

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}
class MouseWithCat extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }
  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }
  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>

        {/*          We could just swap out the <p> for a <Cat> here ... but then          we would need to create a separate <MouseWithSomethingElse>          component every time we need to use it, so <MouseWithCat>          isn't really reusable yet.        */}
        <Cat mouse={this.state} />
      </div>
    );
  }
}
class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <MouseWithCat />
      </div>
    );
  }
}

这种方式的实现可能对个别的场景有用,但是,我们还是没有达成通过封装让这种行为真正地复用的目标。在别的应用场景下,每一次当我们需要获取光标在屏幕上的坐标的时候,我们都需要重新创建一个组件(例如,一个跟<MouseWithCat>相似组件)来完成这个业务场景所对应的渲染任务。

这个时候,就轮到render props 出场啦:相比直接把<Cat>这个组件硬编码到<Mouse>组件当中,刻意地去改变<Mouse>组件的UI输出(也就是我们重新定义一个<MouseWithCat>组件的原因)。更好的做法是,我们可以给<Mouse>组件定义一个值为函数类型的prop,让这个prop自己来动态地决定要在Mouse组件的render方法要渲染东西。这个值为函数类型的prop就是我们所说的render prop了。

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}
class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }
  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }
  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>

        {/*          Instead of providing a static representation of what <Mouse> renders,          use the `render` prop to dynamically determine what to render.        */}
        {this.props.render(this.state)}
      </div>
    );
  }
}
class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

现在,相比每一次都要重复地将<Mouse>组件的代码复制一遍,然后将我们要渲染的东西硬编码到<Mouse>的render方法中去,我们采取了一个更省力的办法。那就是给Mouse新增了一个render属性,让这个属性来决定要在<Mouse>组件中渲染什么。

更加具体和直白地说,一个render prop(这里不是代指技术,而是组件属性) 就是一个值为函数类型的prop。通过这个函数,我们让挂载了这个prop的组件知道自己要去渲染什么。

这种技术使得我们之前想要共享的某些行为(的实现)变得非常之可移植(portable)。假如你想要得到这种行为,你只需要渲染一个带render属性的类<Mouse>组件到你的组件树当中就可以了。剩下的就让这个render prop来获取相关的数据(通过函数形参被实例化时得到。拿上述例子来说,就是(mouse)=> <Cat mouse={mouse}>mouse),然后决定如何干预这个组件的渲染。

一个很有意思的,并值得我们注意的事情是,你完全可以通过一个带render属性的普通组件来实现大部分的HOC。举个例子,假如你在共享行为(监听mousemove事件,获得光标在屏幕上的坐标)时不想通过<Mouse>组件来完成,而是想通过高阶组件withMouse来完成的话,那么就可以很简单地通过创建一个带render prop的<Mouse>组件来达成:

// If you really want a HOC for some reason, you can easily
// create one using a regular component with a render prop!
function withMouse(Component) {
  return class extends React.Component {
    render() {
      return (
        <Mouse render={mouse => (
          <Component {...this.props} mouse={mouse} />
        )}/>
      );
    }
  }
}

可以这么说,render props(指技术)让HOC技术与其他技术(在这里,指它自己)的组合使用成为了可能。

render prop的prop名不一定叫render

如上面的标题,你要牢牢记住,这种技术虽然叫render props,但是prop属性的名称不一定非得叫“render”。实际上,只要组件上的某个属性值是函数类型的,并且这个函数通过自己的形参实例化时获取了这个组件的内部数据,参与到这个组件的UI渲染中去了,我们就说这个组件应用了render props这种技术。

在上面的例子当中,我们一直在使用“render”这个名称。实际上,我们也可以轻易地换成children这个名称!

<Mouse children={mouse => (
  <p>The mouse position is {mouse.x}, {mouse.y}</p>
)}/>

同时,我们也要记住,这个“children”prop不一定非得罗列在在JSX element的“属性”列表中。它实际上就是我们平时用JSX声明组件时的children,因此你也可以像以前一样把它放在组件的内部。

<Mouse>
  {mouse => (
    <p>The mouse position is {mouse.x}, {mouse.y}</p>
  )}
</Mouse>

在react-motion这个库的API中,你会看到这种写法的应用。

因为这种写法比较少见,所以假如你这么做了,为了让看你代码的人不产生疑惑的话,你可能需要在静态属性propTypes中显式地声明一下children的数据类型必须为函数。

Mouse.propTypes = {
  children: PropTypes.func.isRequired
};

注意点

当跟React.PureComponent结合使用时,要当心

如果你在组件的render方法里面创建了一个函数的话,然后把这个函数赋值给这个组件的prop的话,那么得到的结果很有可能是违背了你初衷的。怎么说呢?因为一旦你这么做了,React在作shallow prop comparison的时候,new props都会被判断为不等于old props的。现实是,这么做恰恰会导致在每一次render的调用的时候生成一个新的值给这个属性。

我们继续拿上面的<Mouse>组件作为例子。假如<Mouse>组件继承了React.PureComponent的话,我们的代码应该是像下面这样的:

class Mouse extends React.PureComponent {
  // Same implementation as above...
}
class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>

        {/*          This is bad! The value of the `render` prop will          be different on each render.        */}
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

在上面的代码例子当中,每一次<MouseTracker>组件的render方法被调用的时候,它都会生成一个新的函数实例给<Mouse>组件,作为“render”属性的值。然而,我们之所以继承React.PureComponent,就是想减少<Mouse>组件被渲染的次数。如此一来,<Mouse>因为一个新的函数实例被判定为props已经发生改变了,于是乎进行了不必要的渲染。这与我们的让<Mouse>组件继承React.PureComponent的初衷是相违背的。

为了避开(To get around)这个问题,你可以把render prop的值赋值为<MouseTracker>组件实例的一个方法,这样:

class MouseTracker extends React.Component {
  // Defined as an instance method, `this.renderTheCat` always
  // refers to *same* function when we use it in render
  renderTheCat(mouse) {
    return <Cat mouse={mouse} />;
  }
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={this.renderTheCat} />
      </div>
    );
  }
}

在某些场景下,你可能无法把prop的值静态地赋值为组件实例的某个方法(例如,你需要覆盖组件的props值或者state值,又两者都要覆盖)。那么,在这种情况下,你只能老老实实地让<Mouse>组件去继承React.Component了。

到此这篇关于React RenderProps模式超详细讲解的文章就介绍到这了,更多相关React RenderProps内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • React中props使用介绍

    目录 1.继续深入state 2.props 3.限制Props的传递类型 4.函数接收props参数 5.插槽 1.继续深入state state有两种用法: setState({}) 和 setState(()=>{}) 第一种用法本质是:我写了这个属性,然后进行覆盖操作. 第二种用法好处是:回调函数中的第一个参数是state.这样的话看起来获取到原先state上的数据也更加简单! setState修改数据实现响应式的本质 在每次修改之后,会重新调用render函数. 2.props 什么是

  • React中props使用教程

    目录 1. children 属性 1.1 React.cloneElement方法 1.2 React.Children.map方法 2. 类型限制(prop-types) 3. 默认值(defaultProps) 1. children 属性 概述: children 属性,表示组件标签的子节点,当组件标签有子节点时,props 就会有该属性,与普通的 props 一样,其值可以是任意类型.单标签和双标签中没有数据就没有此属性. 语法: # 父组件 class App extends Rea

  • React 中state与props更新深入解析

    目录 正文 组件的 updater 处理 ClickCounter Fiber 的 update beginWork Reconciling children for the ClickCounter Fiber 处理 Span Fiber 的 update Reconciling children for the span fiber Effects list commit 阶段 应用 effects DOM updates 调用 Post-mutation 生命周期 正文 在这篇文章中,我使

  • React和Vue的props验证示例详解

    目录 React中的props校验 react中单一类型校验器 设定属性类型和默认值 设置必需属性 react中组合类型校验器 PropTypes.oneOfType PropTypes.arrayOf PropTypes.objectOf PropTypes.shape PropTypes.node vue数据验证:通过变量名:具体类型的方法 vue数据验证:带有默认值的方式验证 通过required设置必须属性 多种类型中的一种 对象数组验证,并且数组元素是特定属性的对象 自定义验证函数 V

  • vue和react中props变化后如何修改state

    目录 vue和react中props变化后修改state 改进 react改变state必须知道的知识点 1.不能直接修改state 2.state的更新是异步的 3.state的更新是一个合并的过程 state与不可变对象 vue和react中props变化后修改state 如果只想在 state 更改时重新计算某些数据,比如搜索框案例. vue <template> <div> <input type="text" v-model="filt

  • React 高阶组件与Render Props优缺点详解

    目录 高阶组件 增强型高级组件 注入型高阶组件 高阶组件 VS Render Props 总结 高阶组件 高阶组件(HOC)是一个接受组件作为参数并返回一个新组件的函数,如果多个组件有相同的逻辑,将这些逻辑用函数封装,使它们能跨组件共用,这种用法称为高阶组件.下面的代码演示什么是高阶组件: export default function WithPrintLog(InnerComponent) { return class extends React.Component{ componentDi

  • React RenderProps模式超详细讲解

    目录 正文 使用Render Props来完成关注点分离 render prop的prop名不一定叫render 注意点 render prop是一个技术概念.它指的是使用值为function类型的prop来实现React component之间的代码共享. 如果一个组件有一个render属性,并且这个render属性的值为一个返回React element的函数,并且在组件内部的渲染逻辑是通过调用这个函数来完成的.那么,我们就说这个组件使用了render props技术. <DataProvi

  • React运行机制超详细讲解

    目录 适合人群 写源码之前的必备知识点 JSX 虚拟Dom 原理简介 手写react过程 基本架子的搭建 React的源码 ReactDom.render ReactDom.Component 简单源码 适合人群 本文适合0.5~3年的react开发人员的进阶. 讲讲废话: react的源码,的确是比vue的难度要深一些,本文也是针对初中级,本意了解整个react的执行过程. 写源码之前的必备知识点 JSX 首先我们需要了解什么是JSX. 网络大神的解释:React 使用 JSX 来替代常规的

  • React渲染机制超详细讲解

    目录 准备工作 render阶段 workloopSync beginWork completeWork commit阶段 commitWork mutation之前 mutation fiber树切换 layout layout之后 总结 准备工作 为了方便讲解,假设我们有下面这样一段代码: function App(){ const [count, setCount] = useState(0) useEffect(() => { setCount(1) }, []) const handl

  • Java 超详细讲解设计模式之中的抽象工厂模式

    目录 抽象工厂模式 1.什么是抽象工厂 2.抽象工厂模式的优缺点 3.抽象工厂模式的结构与实现 4.抽象工厂方法模式代码实现 5.抽象工厂模式的应用场景 6.抽象工厂模式的扩展 抽象工厂模式 前面文章介绍的工厂方法模式中考虑的是一类产品的生产,比如案例中的百事可乐工厂只能生产百事可乐,可口可乐工厂只能生产可口可乐,也就是说:工厂方法模式只考虑生产同等级的产品. 1.什么是抽象工厂 在现实生活中许多工厂是综合型的工厂,能生产多种类)的产品,就拿案例里面的可乐来说,在节日的时候可能会有圣诞版的可乐,

  • Java 超详细讲解设计模式之中的建造者模式

    目录 1.什么是建造者模式? 2.建造者模式的定义 3.建造者模式的优缺点 4.建造者模式的结构 5.建造者模式代码演示 6.建造者模式的应用场景 7.建造者模式和工厂模式的区别 1.什么是建造者模式? 我们知道在软件开发过程中有时需要创建一个很复杂的对象,通常由多个子部件按一定的步骤组合而成. 例如,比如我们在自己在组装一台计算机的时候,需要有 CPU.主板.内存.硬盘.显卡.机箱.显示器.键盘.鼠标等部件组装而成的.比如学校需要采购100台计算机,学校不可能自己把零件买过来自己组装,肯定是告

  • Java超详细讲解设计模式之一的工厂模式

    目录 工厂模式 1.简单工厂 1.1结构 1.2实现 1.3优缺点 1.4扩展 2.工厂方法 2.1结构 2.2实现 2.3优缺点 3.抽象工厂 3.1结构 3.2实现 3.3优缺点 4.模式扩展 4.1实现 工厂模式 在Java应用程序中对象无处不在,这些对象都需要进行创建,如果创建的时候直接new对象,那么如果我们要更换对象,所有new对象的地方都需要进行更改.违背了软件设计原则中的开闭原则.如果我们使用工厂生产对象,只需要在工厂中关注对象的改变即可,达到了与对象解耦的目的,工厂模式最大的特

  • Redis超详细讲解高可用主从复制基础与哨兵模式方案

    目录 高可用基础---主从复制 主从复制的原理 主从复制配置 示例 1.创建Redis实例 2.连接数据库并设置主从复制 高可用方案---哨兵模式sentinel 哨兵模式简介 哨兵工作原理 哨兵故障修复原理 sentinel.conf配置讲解 哨兵模式的优点 哨兵模式的缺点 高可用基础---主从复制 Redis的复制功能是支持将多个数据库之间进行数据同步,主数据库可以进行读写操作.当主数据库数据发生改变时会自动同步到从数据库,从数据库一般是只读的,会接收注数据库同步过来的数据. 一个主数据库可

  • Java 超详细讲解设计模式之原型模式讲解

    目录 传统方式 原型模式基本介绍 原型模式在spring框架中源码分析 深入讨论-浅讨论和深拷贝 原型模式的注意事项和细节 传统方式 克隆羊问题 现在有一只羊 tom,姓名为: tom,年龄为:1,颜色为:白色,请编写程序创建和 tom羊属性完全相同的10只羊. 传统方式解决克隆羊问题 思路分析(图解) 代码演示: public class Sheep { private String name; private int age; private String color; public She

  • Java超详细讲解设计模式中的命令模式

    目录 介绍 实现 个人理解:把一个类里的多个命令分离出来,每个类里放一个命令,实现解耦合,一个类只对应一个功能,在使用命令时由另一个类来统一管理所有命令. 缺点:如果功能多了就会导致创建的类的数量过多 命令模式(Command Pattern)是⼀种数据驱动的设计模式,它属于行为型模式.请求以命令的形式包裹在对象中,并传给调⽤对象.调⽤对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执⾏命令. 介绍 意图:将⼀个请求封装成⼀个对象,从⽽使您可以⽤不同的请求对客户进⾏参数化.

  • React useState超详细讲解用法

    目录 前言 基本用法 initData为非函数的情况 initData为函数的情况 state变化监听 过时状态问题 更新引用数据类型 useState 实现原理 前言 React-hooks 正式发布以后, useState 可以使函数组件像类组件一样拥有 state,也就说明函数组件可以通过 useState 改变 UI 视图.那么 useState 到底应该如何使用,底层又是怎么运作的呢,首先一起看一下 useState . 基本用法 [ state , dispatch ] = useS

随机推荐