React 重温之Render Props
背景
React 16+ 版本更新后,新增了许多有意思的功能,作为一个苦逼哈哈的前端,也只能与时俱进的继续学习学习了。
什么是 Render Props
先看官方是怎么说的:
The term “render prop” refers to a simple technique for sharing code between React components using a prop whose value is a function.
翻译成大白话就是:这个技术呢,很简单,就是给组件添加一个值为函数的属性,这个函数可以在组件渲染(render)的时候调用,那这个组件是干啥用的呢?就是为了给原有组件“注入”其它组件的代码。
为啥要有这个东西?
看完官方的说法,想必是一头雾水,为啥要通过这么麻烦的方式给一个组件“注入”另一个组件的代码?React不是原本就支持组件嵌套和传值吗?
人官方网站也说了,这个技术只是用来解决一些比较特殊的问题的,并不是让你没事就用的, 那么我们可能就要问了,什么时候要用呢?
同样套用官方的说法:
a render prop is a function prop that a component uses to know what to render.
翻译过来就是说,这个渲染属性就是让组件知道自己渲染什么东西。。。 这不废话嘛!别急,还有大白话翻译呢。
大白话翻译过来就是说啊:如果你一个组件不知道自己渲染什么东西,或者说你一个组件的基础功能是提供”可变数据源“,具体展示UI可以从外部注入,那么就可以用这个技术了。 是不是还是一脸懵?一头雾?没关系,看个官方例子好了。
官方教程解释
可变数据源组件
首先是一个Mouse组件,可以实时获取鼠标位置并展示出来,那么在这里,这个Mouse组件提供”可变数据源“,也就是跟随鼠标一直变化的鼠标位置。
那你在页面渲染个鼠标位置有什么用啊?谁没事儿盯着你页面看鼠标位置啊?但是我们可用鼠标位置做许多用意思的东西啊,比如说跟随鼠标移动的动画啊,拖动页面元素移动啊什么的。。。
在这里,我们说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> {this.props.render(this.state)} // 这个比较关键 </div> ); } }
我们看到在组件的渲染方法(render)里,没什么有价值的东西,但有一行代码很关键{this.props.render(this.state)}
, 这是什么意思?
很简单,调用Mouse组件的props里的render函数(这个render就是我们前面说的 “一个值为函数的属性” ,叫啥名儿都行,叫render是凑巧了,谁让这个技术起码是Render Props呢)来往这里渲染UI,Mouse只负责把“可变数据源”作为参数给render函数(props里面的属性函数,可不是声明周期的render函数)就好了。至于渲染的内容是什么?那就不是Mouse组件能控制了。
被注入UI组件
然后就是一个使用数据源的组件Cat了,让Cat图片跟随鼠标位置的变化而移动。怎么变?当然是使用Mouse组件提供的可变数据源了。
这个Cat组件就是我们希望渲染在页面的UI了。以后可能会有dog、pig之类的,都需要用到Mouse提供的基础数据,也就是“可变数据源”。但是Cat并不能获取Mouse的数据变动啊,怎么把这俩绑定(联系)在一起呢?
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 }} /> ); } }
绑定?还是调用吧
然后就是绑定的出场了,说绑定有点不太合适,说调用可能更符合实际情况一点,之前我们在Mouse的render函数(生命周期函数)里调用了组件Props上的render属性函数(自定义的,叫啥名儿都行),那么我们在用到Mouse组件的时候,给Mouse组件一个render属性函数,这个函数使用参数(鼠标位置,通过Mouse组件的state传过来)来初始化Cat组件,然后返回。那么这样一来Cat组件就内嵌在Mouse组件的render函数里了。
也即是说一直到这个时候,我们才知道要往Mouse组件里渲染什么UI。
class MouseTracker extends React.Component { render() { return ( <div> <h1>Move the mouse around!</h1> <Mouse render={mouse => ( <Cat mouse={mouse} /> )}/> </div> ); } }
啥时候用
放心,一般情况这个技术用是用不上的,这辈子都不可能用上。。。但还有不一般情况呢不是吗?
简单来说,当两个平级组件之间需要单向依赖的时候,就可以用这个技术了。
那什么是平级依赖呢? 很简单,就是连个同级组件A、B,A组件需要跟随B组件的内部状态来改变自己的内部状态,我们就说A依赖B。为什么是单向依赖呢? 双向依赖怎么办? 双向依赖就把状态维护交给父组件了,谁还那么麻烦啊!
好难啊,能不能不用
当然可以不用了,这技术就不鼓励使用,那有没有别的方式可以避免使用这个技术呢? 当然可以了,单向依赖只是双向依赖的一种简单子集,把需要依赖的状态提到父组件管理就好了,只是麻烦一点而已。
one more thing
如果使用render Props技术的组件(也就是有一个render函数属性的组件)是继承自React.PureComponent 的,那么在props的浅比较看来,新老props总是不一样的,那么就会每次都重新渲染,性能开销比较大。
所以推荐用法如下:
class 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,并使用类函数