术语 “render prop” 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术。具有 render prop 的组件接受一个返回 React 元素的函数,并在组件内部通过调用此函数来实现自己的渲染逻辑。render prop 是一个用于告知组件需要渲染什么内容的函数 prop
在调用组件时,引入一个函数类型的 prop,这个 prop定义了组件的渲染方式。
回顾组件通信的几种方式:
而 render props 本质实际上是使用到了回调的方式来通信。只不过在传统的 js 回调是在构造函数中进行初始化(使用回调函数作为参数),而在 react 中,现在可以通过 props 传入该回调函数,就是我们所介绍的 render prop。
从结果论来说,回调的目的是渲染子组件,而渲染的外部细节需要通过父组件注入,实现了控制反转。
从目的论来说,我们的目的是组件复用。它实现 了内部细节封装,并将外部细节(通过回调函数的形式 )暴露,达到了灵活复用的目的。
<DataProvider render={data => (
<h1>Hello {data.target}</h1>
)}/>
参考博客
官方介绍
组件是 React 代码复用的主要单元,但如何将一个组件封装的状态或行为共享给其他需要相同状态的组件并不总是显而易见。例如,以下组件跟踪 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: '100vh' }} onMouseMove={this.handleMouseMove}>
<h1>移动鼠标!</h1>
<p>当前的鼠标位置是 ({this.state.x}, {this.state.y})</p>
</div>
);
}
}
当光标在屏幕上移动时,组件在
中显示其(x,y)坐标。现在的问题是:我们如何
在另一个组件中复用这个行为?
由于组件是 React 中最基础的代码复用单元
,现在尝试重构一部分代码使其能够在 组件中封装我们需要共享的行为。
// 组件封装了我们需要的行为...
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}>
{/* ...但我们如何渲染 以外的东西? */
}
<p>The current mouse position is ({this.state.x}, {this.state.y})</p>
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<>
<h1>移动鼠标!</h1>
<Mouse />
</>
);
}
}
现在 组件封装了所有关于监听 mousemove 事件和存储鼠标 (x, y) 位置的行为,但其仍不是真正的可复用。
举个例子,假设我们有一个 组件,它可以呈现一张在屏幕上追逐鼠标的猫的图片。我们或许会使用
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: '100vh' }} onMouseMove={this.handleMouseMove}>
{/*
在这里,我们可以简单地使用 来替换 。但是如果这样
做的话,当我们每次遇到这样的情况时,就需要创建一个单独的
。所以 并不是真正的可复用组件。
*/
}
<Cat mouse={this.state} />
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>移动鼠标!</h1>
<MouseWithCat />
</div>
);
}
}
这种方法适用于我们的特定用例,但我们还没有达到以可复用的方式真正封装行为的目标。现在,每当我们想要鼠标位置用于不同的用例时,我们必须创建一个新的组件(本质上是另一个
),它专门为该用例呈现一些东西。现在,我们提供了一个 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}>
{/*
使用 `render`prop 动态决定要渲染的内容,
而不是给出一个 渲染结果的静态表示
*/ }
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>移动鼠标!</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
相比于直接将
写死在
组件中,并且有效地更改渲染的结果,我们可以为
提供一个函数 prop 来动态的确定要渲染什么 —— 一个 render prop。
render prop 一个有趣的事情是你可以使用带有 render prop 的常规组件来实现大多数高阶组件 (HOC)。 例如,如果你更喜欢使用 withMouse HOC而不是 组件,你可以使用带有 render prop 的常规 轻松创建一个:
// 如果你出于某种原因真的想要 HOC,那么你可以轻松实现
// 使用具有 render prop 的普通组件创建一个!
function withMouse(Component) {
return class extends React.Component {
render() {
return (
<Mouse render={mouse => (
<Component {...this.props} mouse={mouse} />
)}/>
);
}
}
}
render props解决了这类问题,能够封装固定的逻辑,动态地确定需要渲染的内容。可以这么理解:A组件中的state、控制state中count计数值的代码逻辑是不变的,改变的只是谁作为A的子组件,有可能是B,也有可能是C。那么我们就可以在需要渲染子组件的地方,留一个空位,将来可以选择渲染B,也可以选择渲染C。这样,就复用了不变的逻辑,又能动态地选择要渲染的内容。
render prop 是因为模式才被称为 render prop ,你不一定要用名为 render 的 prop 来使用这种模式。事实上, 任何被用于告知组件需要渲染什么内容的函数 prop 在技术上都可以被称为 “render prop”。尽管之前的例子使用了 render,我们也可以简单地使用 children prop!
<Mouse children={mouse => (
<p>鼠标的位置是 {mouse.x},{mouse.y}</p>
)}/>
children prop 并不真正需要添加到 JSX 元素的 “attributes” 列表中。相反,你可以直接放置到元素的内部!
<Mouse>
{mouse => (
<p>鼠标的位置是 {mouse.x},{mouse.y}</p>
)}
</Mouse>
由于这一技术的特殊性,当你在设计一个类似的 API 时,你或许会要直接地在你的 propTypes 里声明 children 的类型应为一个函数。
Mouse.propTypes = {
children: PropTypes.func.isRequired
};
如果你在 render 方法里创建函数,那么使用 render prop 会抵消使用 React.PureComponent 带来的优势
。因为浅比较 props 的时候总会得到 false
,并且在这种情况下每一个 render 对于 render prop 将会生成一个新的值
。下面例子中
,每次
渲染,它会生成一个新的函数作为
(箭头函数的) ,因而在同时也抵消了继承自 React.PureComponent 的
组件的效果!
class Mouse extends React.PureComponent {
// 与上面相同的代码......
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
{/*
这是不好的!
每个渲染的 `render` prop的值将会是不同的。
*/}
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
为了绕过这一问题,有时你可以定义一个 prop 作为实例方法,类似这样
class MouseTracker extends React.Component {
// 定义为实例方法,`this.renderTheCat`始终
// 当我们在渲染中使用它时,它指的是相同的函数
renderTheCat(mouse) {
return <Cat mouse={mouse} />;
}
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={this.renderTheCat} />
</div>
);
}
}
如果你无法静态定义 prop(例如,因为你需要控制组件 props 和/或 state 的暴露程度),则
应该继承自 React.Component。
上一篇:react05-生命周期