翻译自:https://reactjs.org/docs/render-props.html
“render props”是实现React组件之间的共享代码的一个技巧。这种方式是利用React组件的属性。而这个属性是一个函数的形式。
一个有render属性的组件,会接受一个函数。这个函数返回一个React元素。这个组件会调用这个函数。1
2
3<DataProvider render={data => (
<h1>Hello {data.target}</h1>
)}/>
也有一些利用render这个属性的第三方库,比如React Router 和 Downshift。
这篇文章,我们将会讨论,为什么render属性非常实用,并且告诉你如何去写自己的render函数。
利用render props解决组件功能复用
Components是React实现代码复用的主要单元。但是,当组件封装另外一个相同状态(state)或者方法(behavior)的组件的时候,通常我们并不非常明白如何共享这些状态和方法。
打个比方说,下面这个组件记录了web app的鼠标位置。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class 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>
);
}
}
当鼠标在屏幕中移动端时候,MouseTracker将鼠标垫(x, y)坐标显示在标签中。
现在这个问题来了,我们该如何在其他组件中复用这个记录鼠标坐标的功能?换句话说,如果其他组件需要知道鼠标的位置,我们能不能封装这个方法,这样我们可以在其他组件中共享了呢?
既然Components是React中的最基本的单元,我们将上面的代码重构了一下。提取出Mouse组件。这个组件封装了记录鼠标坐标的方法。这样我们就可以在别的地方复用了。
1 | // The <Mouse> component encapsulates the behavior we need... |
现在,改进之后的Mouse封装了所有的功能,包括监听鼠标移动时间,存储鼠标坐标(x,y)。但是这样其实并没有达到复用的目的。
如果,我们有一个Cat组件。这个组件会渲染出猫追老鼠的场景。我们可能会用1
<Cat mouse={{ x, y }}>
告诉Mouse组件他们在屏幕上的位置。
第一次,你可以放在里面,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49class 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>
);
}
}
上面的方法对于具体案例来说是有用的,但是这并不是封装可复用的目的。 如果在不同的案例里,我们都想知道老鼠的位置的时候,我们不得不创建一个新的和差不多的
这就是render的目的了。取代之前将Cat硬编码到Mouse里,并有效改变渲染输出,我们提供给Mouse一个属性。这个属性用来动态决定到底什么需要render。这个属性就是render。
1 | class Cat extends React.Component { |
现在,代替了之前的方式,我们提供了render的属性,这样Mouse就可以动态的决定去渲染哪些组件了。
这项技术使我们共享的行为非常便携。为了能够实现共享便携,在渲染Mouse组件的时,利用render属性来告诉Mouse去渲染Cat组件,同时将当前坐标一并传给了Cat。
总而言之,render属性是一个函数属性,这个属性会告知当前组件渲染的内容。
关于render prop还有个非常有趣的关注点,组件的render props可以配合大多数高阶组件。 比如说,如果你更加喜欢用withMouse高阶组件来代替Mouse组件,你可以非常简单利用Mouse的render props:
1 | // If you really want a HOC for some reason, you can easily |
用其他props,而不是render props
必须记住的一点:只要一个props属性是一个函数,并且这个函数告知组件如何渲染,那么这属性就是”render props”。
虽然我们在上面用的是render做到栗子,我们也可以用children:1
2
3<Mouse children={mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}/>
并且,children没有必要作为组件属性,他也可以直接写在组件里面。1
2
3
4
5<Mouse>
{mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}
</Mouse>
你可以在react-motion的API中看到这样的用法。
由于这个方法不太常见,所以你有必要在组件里明确指出children的function类型:1
2
3Mouse.propTypes = {
children: PropTypes.func.isRequired
};
注意事项
React.PureComponent里处理render props要小心
在React.PureComponent的render里创建render props会降低他的性能。这是因为,当oldProps和newProps在做浅层比较时,肯定返回的是false,每次调用方render时,都会创建一个新的值。比如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class 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>
);
}
}
每次,MouseTracker调用render时,Mouse的render都是一个新的function。由于Mouse是PureComponent,所以每次render返回false。为了解决这个问题,可以改成如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class 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而不是React.PureComponent。(自己理解:props属性若是动态的时候,用React.PureComponent会做多关于oldProps和newProps和oldState和newState的两个比较,而这个肯定返回的是false。与其这样,还不如直接 extend React.Component,他不需要做比较,相当于节省了性能)。