在React官方的高级指南中有个Render Props模式,这个模式在写可复用的组件时可能会被用到。那么什么是Render Props模式呢?
Render Props是指一种通过传递函数作为属性来分享组件中数据的技术
Render Props
接下来讲以官方的一个例子来说明。
首先我们有一个组件<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>The current mouse position is ({this.state.x}, {this.state.y})</p>
</div>
);
}
}
复制代码
接下来要创建一个组件<Cat>,<Cat>需要使用鼠标位置信息来更新自身位置,完成鼠标随动的功能。
这么问题就来了,我们要怎么利用<Mouse>类来完成这一功能。
首先最简单的就是直接在<Mouse>类中引入<Cat>, 再把数据传递到<Cat>中
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}>
<Cat mouse={this.state} />
</div>
);
}
}
复制代码
这样我们创建了新了组件<MouseWithCat>,这种方式可以实现功能,但是并没有对<Mouse>进行有效的复用,如果后续有其他需要使用鼠标位置信息的组件,那么我们也要创建一个新的组件。
为了更好的复用<Mouse>组件,我们可以使用Render Props模式。直接看例子:
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 {
// 省略部分代码
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{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>
);
}
}
复制代码
可以看到,<Mouse>进行了重构,让其可以接受一个属性,属性的类型为函数,然后在render阶段去执行这个函数,并把相应的内部参数传递进去。
在使用时,传给render属性一个函数,函数返回<Cat>, 并把函数参数传递给了<Cat>属性。这样我们也完成上述要求的功能,同时如果有其他需要用到鼠标信息的组件,我们都可以复用<Mosue>组件,而不用像之前一样去创建新的组件。
讲完这个例子,我们可以看到Render Props模式的优势所在,它让我们有效的编写可复用的组件
高阶组件
这边也可以编写高阶组件来完成功能。
const WithMouse = (component) => {
return class 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 (
<component {...this.props} mouse={this.state} />
)
}
}
}
复制代码
高阶函数同样也可以完成这个功能,但是灵活性会稍微有些降低,使用Render Props的话,我们可以在那个函数中做一些其他操作而不用改动<Mosue>和<Cat>。
Context
React Context新API中也用到了Render Props,在Consumer中需要传递一个函数来消费数据。
import React, {Component} from 'react'
const ThemeContext = React.createContext('light')
class App extends Component {
render () {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
)
}
}
function Toolbar () {
return (
<div>
<ThemeButton />
</div>
)
}
function ThemeButton (props) {
return (
<ThemeContext.Consumer>
{ theme => <button {...props} theme={theme}>{theme}</button> }
</ThemeContext.Consumer>
)
}
复制代码
总结
React的Render Props还是比较容易理解的,重点是如何在组件的代码中正确的使用。
最后推荐一篇相关的文章:React's Render Props Pattern - Children as a Function