一、描述

Render Props 只是一个概念,其实本质上是将一个方法传递给组件,一般我们给组件传递内容都是传递的值,而值一般都是 对象、Sting、Array、Number、Boolean 。

而有些情况下,可能需要在组件中传递共用方法来实现代码的复用,不过这种情况下传递的就是一个纯粹的方法,比如父组件传递一个 function 给子组件,然后子组件去调用,实现代码的复用或事件的冒泡。

而 render props 则是传递一个渲染逻辑给子组件,带有 render props 的组件,并不是自己的实现的渲染逻辑,而是通过 props 传递实现的渲染逻辑,而这个渲染逻辑是由 render props 方法完成的,比如下面的方法:

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

目前文档中说的是 React RouterDownshift 使用了 render props。

二、Croll-Cutting Concerns 中使用 render props

1、一般情况下组件的复用

React 的组件已经是复用性的一种实现方式了,只不过有些时候,对于逻辑的复用还是比较低的,更多情况下,我们是将逻辑封装到组件中,然后对组件进行复用,然而有些时候,多个组件中可能交叉相同的逻辑,但是这两个组件却不能整合到一个组件中,这种情况就称为 Croll-Cutting Concerns ,也就是 交叉关注点

按照官网给的例子去实践一下,写一个组件,这个组件内部有记录鼠标位置的方法逻辑:


class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
  }
  state = {
    x: 0,
    y: 0
  }
  handleMouse = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  }
  render() {
    return (
      <div style={{height: '100vh', width: '100vw'}} onMouseMove={this.handleMouse}> 
        <h1>Move mouse</h1>
        <h3>ClientX: {this.state.x}</h3>
        <h3>ClientY: {this.state.y}</h3>
      </div>
    )
  }
}

该组件的效果如下:

GIF.gif

目前把这个 MouseTracker 组件放到其他组件中就可以实现重用,也就是组件或者是逻辑重用。

但是如果我现在需要一个组件,其中需要拥有控制显示鼠标的逻辑,只不过我只需要获取 clientXclientY 显示的逻辑可能是不同的,比如一个组件 <Car>

class Cat extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    const {x,y} = this.props;
    return <div style={{position: 'absolute',left:`${x}px`,top:`${y}px`,width:'50px',height: '50px', backgroundColor:'#000'}}/>
  }
}

这个组件是通过传入 props.x 和 props.y 来控制方向的,而在使用的时候,将 Cat 组件放在了 MouseTracker 组件中,比如上面的 MouseTracker 组件的 render 部分:

render() {
    return (
      <div style={{height: '100vh', width: '100vw'}} onMouseMove={this.handleMouse}> 
        <h1>Move mouse</h1>
        <h3>ClientX: {this.state.x}</h3>
        <h3>ClientY: {this.state.y}</h3>
        <Cat {...this.state} />
      </div>
    )
  }

效果如下:

222.gif

虽然实现了组件和逻辑的复用,但这回导致每次都需要将 Cat 组件硬编码到 MouseTracker 中,然后再去传递 props,然后 MouseTracker 的样式可能还会发生变化,其实并没有复用性很高,这样子会造成我但凡需要一个 Cat 类似的组件,就需要硬编码一次。

2、使用 render props

使用 render props 可以避免掉上面的情况,最主要的是避免硬编码,实现动态的渲染 MouseTracker 的子组件。

比如改造上面的 MouseTracker 组件, 改动点只在 render() 中体现:

render() {
    return (
      <div style={{height: '100vh', width: '100vw'}} onMouseMove={this.handleMouse}> 
        <h1>Move mouse</h1>
        <h3>ClientX: {this.state.x}</h3>
        <h3>ClientY: {this.state.y}</h3>
        {this.props.render(this.state)}
      </div>
    )
  }

只改动了上面这部分,{this.props.render(this.state)},通过 props 传递过来的 render 方法然后传递一个 this.state

所以 render prop 是一个组件用来了解要渲染什么内容的函数 prop。

所以在使用的时候,可以如同下面这样,关键是 render={(data) => <Cat {...data} />}

class App3 extends Component {
  render() {
    return (
      <MouseTracker render={(data) =>  <Cat {...data} />} />
    )
  }
}

因此通过 render props,能够真正的实现动态化的渲染。

三、props 关键而非 render 关键

虽然上面使用了 render={(data) => <Cat {...data}>} 这种方式,但是不代表 render 是唯一的名字,在 MouseTracker 组件中,我们使用的是 this.props.render(this.state),也就是说 render 只是个名字,只是为了表明这是一种 render props 思想而使用的。

所以 this.props.render 中 render 可以随便换个名字,只要还是传递一样的渲染方法即可,在 MouseTracker 的 render 中使用:

{this.props.renderPropsNewName(this.state)}

而在使用 MouseTracker 中使用:

 <MouseTracker renderPropsNewName={(data) =>  <Cat {...data} />} />

四、PureComponent 中使用 render props

在 PureComponent 的 shouldComponentUpdate 的浅比较由于每次函数都会返回一个函数句柄,导致每次的比较都是返回 false,所以如果一个组件是 PureComponent,这样子就失去了意义。

如果上面 <MouseTracker> 继承自 React.PureComponent,则会直接导致每次比较都返回 false。

如果要绕过这个问题我之前也提到过,可以使用一个示例方法去定义 props,就像是定义事件处理经常使用的那样:

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.renderTheCat = this.renderTheCat.bind(this);
  }

  renderTheCat(mouse) {
    return <Cat mouse={mouse} />;
  }

  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse renderPropsNewName={this.renderTheCat} />
      </div>
    );
  }
}