Render Props
术语
render prop是指一种在React组件之间使用一个值为了函数的prop共享代码的简单技术。
具有render prop的组件接受一个函数,该函数返回一个React元素并调用它而不是实现自己的渲染逻辑。
<DataProvider render ={ data => ( <h1>hello {data.target}</h1> )}>这里的render仅仅是一个prop的名称,可以任意名称abc... 只需要和DataProvider组件中使用的时候保持一致即可。
使用Render Props解决横切关注点(Cross-Cutting-Concerns)
组件是React代码复用的主要单元,但如何分享一个组件封装到其他相同state组件的状态或行为并不总时很容易。 例如,以下组件跟踪web应用程序中鼠标的位置:
class MouseTracker extends React.Component{
constructor(props){
super(props)
this.handleMouseMove= this.handleMouseMove.bind(this);
console.log(this)
this.state = {
x:0,
y:0
}
}
handleMouseMove(event){
console.log(this)
this.setState({
x:event.clientX,
y:event.clientY
})
}
render(){
return (
<div style={{height:'100vh'}} onMouseMove={this.handleMouseMove}>
<h1> 鼠标当前的位置:({this.state.x}, {this.state.y}) </h1>
</div>
)
}
}当光标在屏幕上移动的时候,组件在<h1>中显示其(x,y)坐标。 现在的问题是:我们如何在另一个组件中复用整个行为呢?换个说法,若另一个组件需要知道鼠标的位置,我们能否封装这一行为,以便轻松地与其他组件共享它? 由于组件是React中最基础的代码复用单元,现在尝试重构一部分代码使其能在<Mouse>组件中封装我们需要共享的行为。
class Mouse extends React.Component{
constructor(props){
super(props)
this.handleMouseMove= this.handleMouseMove.bind(this);
console.log(this)
this.state = {
x:0,
y:0
}
}
handleMouseMove(event){
console.log(this)
this.setState({
x:event.clientX,
y:event.clientY
})
}
render(){
return (
<div style={{height:'100vh'}} onMouseMove={this.handleMouseMove}>
<h1> 鼠标当前的位置:({this.state.x}, {this.state.y}) </h1>
{/* ...但我们如何渲染 <p> 以外的东西? */}
</div>
)
}
}首先考虑的,插入一个子组件,然后将state传入子组件,这样子组件就获取到鼠标的位置:
class Mouse extends React.Component{
constructor(props){
super(props)
this.handleMouseMove= this.handleMouseMove.bind(this);
console.log(this)
this.state = {
x:0,
y:0
}
}
handleMouseMove(event){
console.log(this)
this.setState({
x:event.clientX,
y:event.clientY
})
}
render(){
return (
<div style={{height:'100vh'}} onMouseMove={this.handleMouseMove}>
<h1> 鼠标当前的位置:({this.state.x}, {this.state.y}) </h1>
{/* ...但我们如何渲染 <p> 以外的东西? */}
<Cat mouse={this.state} />
</div>
)
}
}
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 }} />
);
}
}类似这样,效果是实现了,但是我们还是无法复用这个组件,所以我们希望这个子组件是传入的,并且我们能向这个组件传递一个值作为props属性。 现在需要解决两个问题:
- 组件是通过props传入的
- 父组件传入的这个组件需要接受一个子组件传递的参数
结合这两个要求,只有传递一个函数,这个函数接受一个参数并返回接受这个参数作为prop的组件。 所以最终调用mouse的时候是这样的:
<Mouse diyrender = {data => <Cat mouse={data}>}>Mouse中render对应修改成这样:
render(){
return (
<div style={{height:'100vh'}} onMouseMove={this.handleMouseMove}>
<h1> 鼠标当前的位置:({this.state.x}, {this.state.y}) </h1>
{this.props.diyrender(this.state)}
</div>
)
}需要特别强调的是,这里的diyrender只是一个普通的prop的名称,并没有特殊含义。
完整代码:
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}>
<h1> 鼠标当前的位置:({this.state.x}, {this.state.y}) </h1>
{this.props.diyrender(this.state)}
</div>
)
}
}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 }} ></img>
);
}
}使用 Props 而非 render
重要的是要记住,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 Props与React.PureComponent一起使用的时候要小心 如果你在render方法里创建函数,那么使用render prop会抵消使用React.PureComponent带来的优势。因为浅比较props的时候总会得到false,并且在这种情况下每一个render对于render prop将会生成一个新的值。 例如,继续我们之前使用的 <Mouse> 组件,如果 Mouse 继承自 React.PureComponent 而不是 React.Component,我们的例子看起来就像这样:
class Mouse extends React.PureComponent {
// 与上面相同的代码......
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)} />
</div>
);
}
}在这样的例子中,每次<MouseTracker>渲染,都会生成一个新的匿名函数作为Mouse的render,因而在同时也抵消了继承自React.PureComponent的Mouse组件效果。 为了绕过这问题,我们可以定义一个实例方法作为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),则 <Mouse> 应该扩展 React.Component。
