很多人应该看到官方的高阶指南, 新加了 “render props” 的设计模式, 注意这只是一个设计模式, 不是新的 api, 而且有意思的是,2016年年底的时候, 我看到一个 sortable 的列表的 react 开源库代码时候, 就见识了这种写法,当时很不理解。因为 react 灵活性很大,code resusable 的方式有很多种,最开始的 mixin,cloneElement,props.children,HOC,可能会有细微的区别,这里我们讲讲 render props 的设计模式的特别的地方。(匆匆写完,如有错误请指出)
基本使用
官网的例子解释有一点啰嗦,我们来个精简版,我们需要一个显示鼠标位置的 MouseTracker:
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 (
Move the mouse around!
The current mouse position is ({this.state.x}, {this.state.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 (
The current mouse position is ({this.state.x}, {this.state.y})
);
}
}
// 此时 mouseTracker 变成了这样
class MouseTracker extends React.Component {
render() {
return (
Move the mouse around!
);
}
}
完成:
// p 标签的部分用 this.props.render() 代替
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 (
{this.props.render(state)}
);
}
}
// 定义一个 UI 组件,也就是我们希望替换 p 标签的那一段
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
);
}
}
// 改写后的 MouseTracker
class MouseTracker extends React.Component {
render() {
return (
Move the mouse around!
(
)}/>
);
}
}
额外提及
注意的是 render props 如上面所说是个设计模式, 所以没有必要一定要取名叫 render,官网举例说你可以用 children 作为 prop 的名字,这样你甚至都不用显示去声明它,如下:
class MouseTracker extends React.Component {
render() {
return (
Move the mouse around!
{mouse => (
The mouse position is {mouse.x}, {mouse.y}
)}
);
}
}
说实话,我觉得不举这个例子也许不会困惑,举了反倒。。。
这和普通的 children 有什么区别?先回忆下普通 children 的使用,如下:
class Header extends React.Component {
render() {
return {this.props.children}
}
}
可以看到通常的 children 其实是个 react element(不需要函数调用),但 render props 模式中的 children 是个函数,所以如果你要用这种特殊技巧,最好在 propTypes 里面把 children 声明成一个函数:
Mouse.propTypes = {
children: PropTypes.func.isRequired
};
另外一点就是,官网提到一个坑就是 pureComponent,也是一行注释能说清楚的:
class MouseTracker extends React.Component {
render() {
return (
Move the mouse around!
// 因为每次传进去的render是个新的匿名函数,如果 Mouse 是 PureComponent, 那么只会浅比较
// 每次新的匿名函数和上次永远不会相等,shouldComponentUpdate 永远为 false
// 所以 MouseTracker 每次重新render, Cat 里面会重新执行 this.props.render()
(
)}/>
);
}
}
如果要避免,那当然是把render方法用变量存一下:
class MouseTracker extends React.Component {
renderTheCat(mouse) {
return ;
}
render() {
return (
Move the mouse around!
// render 函数不会发生变化哦
);
}
}
Props render 模式在 React Router 里面的使用
render props 这种设计模式在 react-router 4.0 里面使用到了, 之所以要写这个,我是觉得看明白这个,会在你理解 render props 时少走弯路。(贡献 render props 官方文档的和 react-router 的开发者就是同一个人,有兴趣可以查一下,有亮点~)
说实话我之前在用的时候一直以为 render 既然是传一个函数,那么就是一个 stateless component 嘛, 而 component 传入就是传一个 stateful component 嘛,真是 too young too simple。首先我们看一个 demo:
const Home = () => home
// route1
home}/> // route2
这样写是有差别,但是如果你把 render 里面的传参也指向 Home,route1 和 route2 有区别吗??
============= think about it ===========================
如果你答不上来,我把有关源码以及注释贴一下,你可能就明白了。
if (component)
// We already know the differences:
// React.createElement(component)
// React.createElement(() => )
return match ? React.createElement(component, props) : null
if (render)
return match ? render(props) : null
render 并不是一个 component,虽然这个函数看上去很像 stateless component,但是并不是走 React.createElement 组件逻辑,而是单纯的返回一个 element。所以第一个 route 的 render 的 element 的 type 是 Home,而第二个是 div。在这个例子里面看不出什么差别,但是如果我们要传入自己的 props,如果我们这么写:
}
/> // route3
}
/> // route4
如之前提到的,每次 Route 所在的组件 render 一次,会形成一个新的匿名函数。所以针对 route3, component 传参每次都发生变化,type 也会发生变化。但是 route4 由于只是执行函数返回 Home,render 的 element 的 type 永远是 Home。而 React 虚拟节点比较根据之一就是 type 和之前的 type 是不是一样。所以 route3 里面的 Home 会随着父组件 render 经历 unmount 和 mount 生命周期,而 route4 里面的 Home 不会
有点绕? 请看代码:https://codesandbox.io/s/m72rolk4mx
并附上官方解释:
所以在有些库里面 render props 的入参很像一个 stateless 组件,比如 Formik, 但实际上走的是函数调用的逻辑。从我个人来讲,虽然 render props 灵活性很大,但是有一点隐性。以上就是 render props 的总结吧!