React - Render Props(使用 props 渲染内容)

目录

  1. 1. Use Render Props for Cross-Cutting Concerns(使用 render props 来解决横切关注点)
  2. 2. Using Props Other Than render(可以使用任意的 props,而不仅仅名为 render 的 prop)
  3. 3. Caveats(注意事项)
    1. 3.1. Be careful when using Render Props with React.PureComponent(将 Render Props 与React.PureComponent 一起使用时要小心)

翻译自 React Docs: Render Props

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

术语 “render prop” 指的是使用其值为函数的 prop 在 React 组件之间共享代码的技术。

A component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic.

具有 render prop 的组件接受一个函数,该函数返回一个React元素并调用它而不是自己实现渲染逻辑。

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

Libraries that use render props include React Router and Downshift.

使用 render props 的库有 React RouterDownshift

In this document, we’ll discuss why render props are useful, and how to write your own.

本文,我们将讨论为什么 render props 很有用,以及如何编写。

Use Render Props for Cross-Cutting Concerns(使用 render props 来解决横切关注点)

Components are the primary unit of code reuse in React, but it’s not always obvious how to share the state or behavior that one component encapsulates to other components that need that same state.

组件是 React 中代码重用的主要单元,但是将一个组件封装的状态或行为共享给其它组件并容易。

For example, the following component tracks the mouse position in a web app:

例如,下面这个组件跟踪 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>
);
}
}

As the cursor moves around the screen, the component displays its (x, y) coordinates in a <p>.

当光标在屏幕上移动时,组件在 <p> 中显示其(x,y)坐标。

Now the question is: How can we reuse this behavior in another component? In other words, if another component needs to know about the cursor position, can we encapsulate that behavior so that we can easily share it with that component?

现在问题是:我们如何在另一个组件中重用此行为?换句话说,如果另一个组件需要知道光标位置,我们是否可以封装该行为,以便我们可以轻松地与其它组件共享此行为?

Since components are the basic unit of code reuse in React, let’s try refactoring the code a bit to use a <Mouse> component that encapsulates the behavior we need to reuse elsewhere.

由于组件是 React 中代码重用的基本单元,让我们试着重构代码,使用 <Mouse> 组件来封装我们需要在其他地方重用的行为。

// The <Mouse> component encapsulates the behavior we need...
// <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> 元素?*/}
<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>
);
}
}

Now the <Mouse> component encapsulates all behavior associated with listening for mousemove events and storing the (x, y) position of the cursor, but it’s not yet truly reusable.

现在,<Mouse> 组件封装了侦听 mousemove 事件,存储了与光标的位置(x,y)相关的所有行为,但它还不能真正重用。

For example, let’s say we have a <Cat> component that renders the image of a cat chasing the mouse around the screen. We might use a

mouse

例如,假设我们有一个 `<Cat>` 组件,它可以呈现猫在屏幕上追逐鼠标的图像。我们可能会使用 ```<Cat mouse = {{x,y}}>``` prop 来告诉组件鼠标的坐标,以便知道将图像放在屏幕上的位置。



As a first pass, you might try rendering the `<Cat>`*inside `<Mouse>`’s `render` method*, like this:

第一版的代码,你可能尝试在 `<Mouse>` 的 render 方法中渲染 `<Cat>`:

```js
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.
我们只需要把这里的 <p> 换成 <Cat> here ... but then
但是这样的话,每次我们需要这种行为都需要重新创建一个 <MouseWithSomethingElse>
组件,很明显这样仍然没有实现更好地复用
*/}
<Cat mouse={this.state} />
</div>
);
}
}

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

This approach will work for our specific use case, but we haven’t achieved the objective of truly encapsulating the behavior in a reusable way. Now, every time we want the mouse position for a different use case, we have to create a new component (i.e. essentially another <MouseWithCat>) that renders something specifically for that use case.

这种方法适用于我们的特定用例,但还没有达到真正可重用的封装行为的目标。现在,每当我们想要在不同的用例中使用鼠标位置时,我们必须创建一个新的组件专门渲染此用例(其本质上是另一个 <MouseWithCat>)。

Here’s where the render prop comes in: Instead of hard-coding a <Cat> inside a <Mouse> component, and effectively changing its rendered output, we can provide <Mouse> with a function prop that it uses to dynamically determine what to render–a render prop.

这就是 render props 的用武之地:我们可以为 <Mouse> 提供一个函数类型的 prop,它可以动态地确定要渲染的内容,而不是硬编码写在 <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 }} />
);
}
}

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.
与提供静态的渲染内容相比较,使用 `render` prop 来决定要渲染的内容更好
*/}
{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>
);
}
}

Now, instead of effectively cloning the <Mouse> component and hard-coding something else in its render method to solve for a specific use case, we provide a render prop that <Mouse> can use to dynamically determine what it renders.

现在,我们通过提供了一个名为 render 的 prop, <Mouse> 组件用它来确定它需要动态地渲染的内容,而不是克隆 <Mouse> 组件并在其render方法中对其渲染的内容进行硬编码。

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

更具体地说,render props 是组件用来知道渲染什么内容的函数 prop。

This technique makes the behavior that we need to share extremely portable. To get that behavior, render a <Mouse> with a render prop that tells it what to render with the current (x, y) of the cursor.

这种技术使我们共享行为非常便携。要获得该行为,只需要使用一个 render prop 渲染 <Mouse>,该 prop 知道如何使用光标的当前(x,y)渲染内容。

One interesting thing to note about render props is that you can implement most higher-order components (HOC) using a regular component with a render prop. For example, if you would prefer to have a withMouse HOC instead of a <Mouse> component, you could easily create one using a regular <Mouse> with a render prop:

关于 render props 的一个有趣的事情是, 你可以使用具有 render props 的常规组件来实现大多数的 高阶组件 (HOC)。例如,如果你希望使用一个 withMouse 的 HOC 而不是 <Mouse> 组件,你可以使用 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!
// 如果你因为某些原因想要实现一个高阶组件,你可以很容易的使用一个接收
// render props 的常规的组件来创建一个
function withMouse(Component) {
return class extends React.Component {
render() {
return (
<Mouse render={mouse => (
<Component {...this.props} mouse={mouse} />
)}/>
);
}
}
}

So using a render prop makes it possible to use either pattern.

因此,使用渲染道具不影响使用其它模式。

Using Props Other Than render(可以使用任意的 props,而不仅仅名为 render 的 prop)

It’s important to remember that just because the pattern is called “render props” you don’t have to use a prop named render to use this pattern. In fact, any prop that is a function that a component uses to know what to render is technically a “render prop”.

你需要知道的重要的一点是,这种模式被称为 “render props”,而非你必须使用名为 render 的 prop 来使用此模式。实际上,任意 的被组件用来渲染其内容的函数的 prop 在技术上都是 “render props”

Although the examples above use render, we could just as easily use the children prop!

虽然上面的例子使用 render 属性,但也可以很很容易地使用 children props!

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

And remember, the children prop doesn’t actually need to be named in the list of “attributes” in your JSX element. Instead, you can put it directly inside the element!

请记住, children prop 不需要在JSX元素的“属性”列表中命名。相反地,你可以把它直接放在元素中!

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

You’ll see this technique used in the react-motion API.

你能在 react-motion API中看到此技术。

Since this technique is a little unusual, you’ll probably want to explicitly state that children should be a function in your propTypes when designing an API like this.

由于这种技术不常见,因此在设计像这样的 API 时,你最好明确在 propTypes 声明 children 应该是一个函数类型。

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

Caveats(注意事项)

Be careful when using Render Props with React.PureComponent(将 Render Props 与React.PureComponent 一起使用时要小心)

Using a render prop can negate the advantage that comes from using React.PureComponent if you create the function inside a render method. This is because the shallow prop comparison will always return false for new props, and each render in this case will generate a new value for the render prop.

如果在 render 方法中创建函数,则使用 render prop 可以会忽视使用 React.PureComponent 所带来的好处。这是因为React.PureComponent 对 props 的浅比较将始终为每次渲染新生成的函数的比较结果返回 false,在这种情况下每次渲染将为 render props 生成新值。

For example, continuing with our <Mouse> component from above, if Mouse were to extend React.PureComponent instead of React.Component, our example would look like this:

例如,对于上面的 <Mouse> 组件,如果 Mouse 要扩展 React.PureComponent 而不是React.Component,我们的示例将如下所示:

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.
这很糟糕,`render` prop 的值在每次渲染的时候都会是一个不同的值
*/}
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}

In this example, each time <MouseTracker> renders, it generates a new function as the value of the <Mouse render> prop, thus negating the effect of <Mouse> extending React.PureComponent in the first place!

在这个例子中,每次 <MouseTracker> 渲染时,它都会生成一个新函数作为 <Mouse render> 的 prop的值,从而使最初 <Mouse> 扩展自 React.PureComponent 的优化效果失效!

To get around this problem, you can sometimes define the prop as an instance method, like so:

要解决此问题, render props 可以将 prop 定义为实例方法,如下所示:

class MouseTracker extends React.Component {
// Defined as an instance method, `this.renderTheCat` always
// refers to *same* function when we use it in render
// 定义为实例方法,`this.renderTheCat` 在渲染师将会一直引用同一个方法
renderTheCat(mouse) {
return <Cat mouse={mouse} />;
}

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

In cases where you cannot define the prop statically (e.g. because you need to close over the component’s props and/or state) <Mouse> should extend React.Component instead.

如果 render props 无法是静态定义prop(例如,因为 render props 需要关闭组件的 props 和/或状态),那么,<Mouse> 应该扩展 React.Component

知识共享许可协议 知识共享许可协议 知识共享许可协议 本网站原创内容(非转载文章)采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。