React 重温之Render Props

背景

React 16+ 版本更新后,新增了许多有意思的功能,作为一个苦逼哈哈的前端,也只能与时俱进的继续学习学习了。

什么是 Render Props

先看官方是怎么说的:

The term “render prop” refers to a simple technique for sharing code between React components using a prop whose value is a function.

翻译成大白话就是:这个技术呢,很简单,就是给组件添加一个值为函数的属性,这个函数可以在组件渲染(render)的时候调用,那这个组件是干啥用的呢?就是为了给原有组件“注入”其它组件的代码。

为啥要有这个东西?

看完官方的说法,想必是一头雾水,为啥要通过这么麻烦的方式给一个组件“注入”另一个组件的代码?React不是原本就支持组件嵌套和传值吗?

人官方网站也说了,这个技术只是用来解决一些比较特殊的问题的,并不是让你没事就用的, 那么我们可能就要问了,什么时候要用呢?

同样套用官方的说法:

a render prop is a function prop that a component uses to know what to render.

翻译过来就是说,这个渲染属性就是让组件知道自己渲染什么东西。。。 这不废话嘛!别急,还有大白话翻译呢。

大白话翻译过来就是说啊:如果你一个组件不知道自己渲染什么东西,或者说你一个组件的基础功能是提供”可变数据源“,具体展示UI可以从外部注入,那么就可以用这个技术了。 是不是还是一脸懵?一头雾?没关系,看个官方例子好了。

官方教程解释

可变数据源组件

首先是一个Mouse组件,可以实时获取鼠标位置并展示出来,那么在这里,这个Mouse组件提供”可变数据源“,也就是跟随鼠标一直变化的鼠标位置。

那你在页面渲染个鼠标位置有什么用啊?谁没事儿盯着你页面看鼠标位置啊?但是我们可用鼠标位置做许多用意思的东西啊,比如说跟随鼠标移动的动画啊,拖动页面元素移动啊什么的。。。

在这里,我们说Mouse组件提供“可变数据源”,但是它并不知道自己要渲染什么,它只是一个基础数据的提供者

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>
        {this.props.render(this.state)} // 这个比较关键
      </div>
    );
  }
}

我们看到在组件的渲染方法(render)里,没什么有价值的东西,但有一行代码很关键{this.props.render(this.state)}, 这是什么意思?

很简单,调用Mouse组件的props里的render函数(这个render就是我们前面说的 “一个值为函数的属性” ,叫啥名儿都行,叫render是凑巧了,谁让这个技术起码是Render Props呢)来往这里渲染UI,Mouse只负责把“可变数据源”作为参数给render函数(props里面的属性函数,可不是声明周期的render函数)就好了。至于渲染的内容是什么?那就不是Mouse组件能控制了。

被注入UI组件

然后就是一个使用数据源的组件Cat了,让Cat图片跟随鼠标位置的变化而移动。怎么变?当然是使用Mouse组件提供的可变数据源了。

这个Cat组件就是我们希望渲染在页面的UI了。以后可能会有dog、pig之类的,都需要用到Mouse提供的基础数据,也就是“可变数据源”。但是Cat并不能获取Mouse的数据变动啊,怎么把这俩绑定(联系)在一起呢?

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

绑定?还是调用吧

然后就是绑定的出场了,说绑定有点不太合适,说调用可能更符合实际情况一点,之前我们在Mouse的render函数(生命周期函数)里调用了组件Props上的render属性函数(自定义的,叫啥名儿都行),那么我们在用到Mouse组件的时候,给Mouse组件一个render属性函数,这个函数使用参数(鼠标位置,通过Mouse组件的state传过来)来初始化Cat组件,然后返回。那么这样一来Cat组件就内嵌在Mouse组件的render函数里了。

也即是说一直到这个时候,我们才知道要往Mouse组件里渲染什么UI。

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

啥时候用

放心,一般情况这个技术用是用不上的,这辈子都不可能用上。。。但还有不一般情况呢不是吗?

简单来说,当两个平级组件之间需要单向依赖的时候,就可以用这个技术了。

那什么是平级依赖呢? 很简单,就是连个同级组件A、B,A组件需要跟随B组件的内部状态来改变自己的内部状态,我们就说A依赖B。为什么是单向依赖呢? 双向依赖怎么办? 双向依赖就把状态维护交给父组件了,谁还那么麻烦啊!

好难啊,能不能不用

当然可以不用了,这技术就不鼓励使用,那有没有别的方式可以避免使用这个技术呢? 当然可以了,单向依赖只是双向依赖的一种简单子集,把需要依赖的状态提到父组件管理就好了,只是麻烦一点而已。

one more thing

如果使用render Props技术的组件(也就是有一个render函数属性的组件)是继承自React.PureComponent 的,那么在props的浅比较看来,新老props总是不一样的,那么就会每次都重新渲染,性能开销比较大。

所以推荐用法如下:

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

继承React.Component,并使用类函数

相关推荐