React: Pattern: Reusable Component: Render Props

20th August 2020 at 2:19pm
React: Pattern: Reusable Component

Render Props 可以看作一种动态的 containment。Containment 是将一个内层组件作为外层组件的 children,而 render props 是将内层组件本身通过一个 render 函数展示在外层组件中:

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: '100vh' }} 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 (
      <Mouse render={mouse => (
        <Cat mouse={mouse} />
      )}/>
    );
  }
}

惯常的用法是,提供一个 render 函数,它接受外层组件的 state 作为参数,返回一个内层组件的实例。即上面代码中的:

<Mouse render={mouse => (
  <Cat mouse={mouse} />
)}/>

HOC 类似,也可以用 withXXX 模式来代码上面的 MouseTracker

function withMouse(Component) {
  return class extends React.Component {
    render() {
      return (
        <Mouse render={mouse => (
          <Component {...this.props} mouse={mouse} />
        )}/>
      );
    }
  }
}

const MouseTracker = withMouse(Cat);

当然你可以不用 render 作为函数名,用 children 或者其他有意义的名字也可以,但它们都被称为 render props。在你的组件 API 中,应该指明你所使用的 render props 是必须的:

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

同时注意使用 render props 可能会导致 React.PureComponent 的好处被抵消。参考 React 文档