preface

This article is free translation, translation process mixed with my own understanding, if misleading, please give up reading.

原文 标 题 :Render Props

Render Prop is a technical concept. It refers to code sharing between React Components using prop with a value of type function.

If a component has a Render attribute and the render attribute is a function that returns a React Element, the render logic inside the component is done by calling that function. So, let’s say this component uses the Render props technology.

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

Several libraries use this technique, such as React Router and Downshift.

In this document, we will discuss why render props are so useful and how you can write your own Render props component.

The body of the

Use Render Props to accomplish separation of concerns

In React, components are the basic unit of code reuse (again, official documentation continually stresses this principle). As of now, there is no clear solution in the React community for sharing state or for some similar behavior (for example, encapsulating one component into another that has the same state).

For example, the following component is used to track mouse position in a Web application:

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>); }}Copy the code

As the cursor moves across the screen, this component will display the current x and y coordinates of the cursor in the

TAB of the document.

The question then becomes: how can we reuse this behavior in other components (i.e. listening for mouseMove events to get cursor coordinates)? In other words, if other components also need to know the current cursor coordinate value, can we encapsulate this behavior and use it in another component right out of the box?

Because, in React, components are the basic unit of code reuse (again). Well, let’s refactor the code to encapsulate the behavior we need to reuse into the

component.

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

Now, the

component seems to encapsulate all the activities related to listening for mousemove events, saving cursor coordinates, and so on. In fact, it’s not really reusable yet.

Suppose we need to implement a component. It needs to render a graphical cat chasing a cursor across the screen. We might get the current cursor position by passing a prop called mouse (whose value is {{x,y}}) to the

component.

First, we’ll insert the

component into the Render method of the

component, like this:

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>
    );
  }
}
Copy the code

This approach may be useful for individual scenarios, but we still haven’t achieved the goal of making this behavior truly reusable through encapsulation. In other application scenarios, every time we need to retrieve the cursor coordinates on the screen, we need to create a new component (for example, one similar to

) to perform the rendering task for the business scenario.

At this point, it’s the turn of the render props: to intentionally change the UI output of the

component rather than hardcoding the

component directly into the

component (that’s why we’re redefining a

component). Better yet, we could define a prop for the

component with a value of function type and let the prop itself dynamically decide what to render in the Render method of the Mouse component. Render prop = render prop; 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>
    );
  }
}
Copy the code

Now, instead of repeating the code of the

component each time, and then hard-coding what we want to render into the Render method of

, we take a more labor-intensive approach. Add a Render attribute to Mouse that determines what to render in the

component.


To be more specific and straightforward, a Render Prop (not a technology, but a component property) is a prop with a value of function type. With this function, we let the component that has the prop mounted know what to render.

This technology makes certain behaviors that we previously wanted to share very portable. If you want to get this behavior, you simply render a class

component with the Render attribute into your component tree. The rest is left to the Render Prop to retrieve the relevant data (obtained when the function parameters are instantiated). In the example above, (mouse) =>

mouse), and then decide how to intervene in the rendering of this component.

One interesting thing to note is that you can implement most HOC with a plain component with the Render attribute. For example, if you want to share behavior (listen for mousemove events, get cursor coordinates on the screen) instead of using the

component, you want to use the higher-order component withMouse, This can be done simply by creating a

component with Render Prop:

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

So to speak, the render props make possible the combination of HOC technology with other technologies (in this case, itself).

render propProp name not necessarily called “render”

Keep in mind that the technology is called render props, but the name of the prop property doesn’t have to be “Render.” In fact, a component is said to be using render props whenever a property value on a component is of function type and the function is using its parameter instantiation to get internal data of the component and participate in the UI rendering of the component.

In the above example, we have been using the name “render”. In fact, we could just as easily change the name to children!

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

Also, keep in mind that the “children” prop doesn’t have to be listed in the “properties” list of the JSX Element. It is actually the children that we normally declare the component with JSX, so you can put it inside the component as before.

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

You’ll see this in action in the React-Motion library API.

Since this is rare, if you do this, you might want to explicitly declare in the static propTypes property that the children data type must be a function, in order not to confuse anyone looking at your code.

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

Pay attention to the point

Be careful when used with the React.PureComponent

If you create a function in the render method of a component and then assign that function to the prop of the component, you will probably get the wrong result. How can I put it? Because once you do this, the React new props will be judged to be different from the old props when making a shallow prop comparison. In reality, doing so will result in a new value being generated for the property every time render is called.

Let’s continue with the

component above as an example. If the

component inherited the react. PureComponent, our code would look like this:

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>
    );
  }
}
Copy the code

In the above code example, each time the render method of the

component is called, it generates a new instance of the function to the

component as the value of the “render” property. However, we inherit the React.PureComponent to reduce the number of times the

component is rendered. As a result,

was forced to decide that the props had changed because of a new function instance, and rendered unnecessarily. This runs counter to our intention of having the

component inherit from the React.PureComponent.




To get around this problem, you can assign the value of Render Prop To a method of the

component instance like this:

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>
    );
  }
}
Copy the code

In some scenarios, you may not be able to statically assign the value of a prop to a method of a component instance (for example, you need to override the props or state of the component, or both). Well, in that case, you’ll just have to let the

component inherit react.component.component.component.react.component.react.component.react.component.react.component.react.ponent