作者简介
何鸣明,携程境外专车研发部前端工程师,主要负责境外打车、包车业务。喜欢交流分享。
一、在谈 react hook 之前
React的组件化给前端开发带来了前所未有的体验,我们可以像玩乐高玩具一样将组件堆积拼接起来,组成完整的UI界面,在加快开发速度的同时又提高了代码的可维护性。
但是随着业务功能复杂度提高,业务代码不得不和生命周期函数糅合到一起。这样很多重复的业务逻辑代码很难被抽离出来,为了快速开发不得不Ctrl+C,如果业务代码逻辑发生变化时,我们又不得不同时修改多个地方,极大的影响开发效率和可维护性。为了解决这个业务逻辑复用的问题,React官方也做了很多努力。
1.1 React.mixin
React mixin 是通过React.createClass创建组件时使用的,现在主流是通过ES6方式创建react组件,官方因为mixin不好追踪变化以及影响性能,所以放弃了对其支持,同时也不推荐使用。这里简单介绍下mixin。
mixin的原理其实就是将[mixin]里面的方法合并到组件的prototype上。
javascript
var logMixin = {
alertLog:function(){
alert('alert mixin...')
},
componentDidMount:function(){
console.log('mixin did mount')
}
}
var MixinComponentDemo = React.createClass({
mixins:[logMixin],
componentDidMount:function(){
document.body.addEventListener('click',()=>{
this.alertLog()
})
console.log('component did mount')
}
})
// 打印如下
// component did mount
// mixin did mount
// 点击页面
// alert mixin
可以看出来mixin就是将logMixn的方法合并到MixinComponentDemo组件中,如果有重名的生命周期函数都会执行(render除外,如果重名会报错)。但是由于mixin的问题比较多这里不展开讲。点击了解更多。
1.2 高阶组件
组件是 React 中代码复用的基本单元。但你会发现某些模式并不适合传统组件。
例如:我们有个计时器和日志记录组件
javascript
class LogTimeComponent extends React.Component{
constructor(props){
super(props);
this.state = {
index: 0
}
this.show = 0;
}
componentDidMount(){
this.timer = setInterval(()=>{
this.setState({
index: ++index
})
},1000)
console.log('组件渲染完成----')
}
componentDidUpdate(){
console.log(`我背更新了${++this.show}`)
}
componentWillUnmount(){
clearInterval(this.timer)
console.log('组件即将卸载----')
}
render(){
return(
<div>
<span>{`我已经显示了:${this.state.index}s`}</span>
</div>
)
}
}
上面就实现了简单的日志和计时器组件。那么问题来了,假如有三个组件分别是LogComponent(需要记录日志)、SetTimeComponent(需要记录时间)、LogTimeShowComponent(日志和时间都需要记录),怎么处理呢?把上面逻辑 Ctrl+C 然后 Ctrl+V 吗?如果记录日志的文案改变需要每个组件都修改么?官方给我们提供了高阶组件(HOC)的解决方案:
javascript
function logTimeHOC(WrappedComponent,options={time:true,log:true}){
return class extends React.Component{
constructor(props){
super(props);
this.state = {
index: 0
}
this.show = 0;
}
componentDidMount(){
options.time&&this.timer = setInterval(()=>{
this.setState({
index: ++index
})
},1000)
options.log&&console.log('组件渲染完成----')
}
componentDidUpdate(){
options.log&&console.log(`我背更新了${++this.show}`)
}
componentWillUnmount(){
this.timer&&clearInterval(this.timer)
options.log&&console.log('组件即将卸载----')
}
render(){
return(<WrappedComponent {...this.state} {...this.props}/>)
}
}
}
logTimeHOC就是一个函数,接受一个组件返回一个新的组件(其实高阶组件就是一个函数)。我们用这个高阶组件来构建我们上面的三个组件:
LogComponent:打印日志组件
javascript
class InnerLogComponent extends React.Component{
render(){
return(
<div>我是打印日志组件</div>
)
}
}
// 使用高阶组件`logTimeHOC`包裹下
export default logTimeHOC(InnerLogComponent,{log:true})
SetTimeComponent:计时组件
javascript
class InnerSetTimeComponent extends React.Component{
render(){
return(
<div>
<div>我是计时组件</div>
<span>{`我显示了${this.props.index}s`}</span>
</div>
)
}
}
// 使用高阶组件`logTimeHOC`包裹下
export default logTimeHOC(InnerSetTimeComponent,{time:true})
LogTimeShowComponent:计时+打印日志组件
javascript
class InnerLogTimeShowComponent extends React.Component{
render(){
return(
<div>
<div>我是日志打印+计时组件</div>
</div>
)
}
}
// 使用高阶组件`logTimeHOC`包裹下
export default logTimeHOC(InnerLogTimeShowComponent)
这样不仅复用了业务逻辑提高了开发效率,同时还方便后期维护。当然上面的案例只是为了举例而写的案例,实际场景需要自己去合理抽取业务逻辑。高阶组件虽然很好用,但是也有一些自身的缺陷:
高阶组件的props都是直接透传下来,无法确实子组件的props的来源。
可能会出现props重复导致报错。
组件的嵌套层级太深。
会导致ref丢失。
二、React Hook
上面说了很多,无非就是告诉我们已经有解决功能复用的方案了。为啥还要React Hook这个呢?上面例子可以看出来,虽然解决了功能复用但是也带来了其他问题。
由此官方带来React Hook,它不仅仅解决了功能复用的问题,还让我们以函数的方式创建组件,摆脱Class方式创建,从而不必在被this的工作方式困惑,不必在不同生命周期中处理业务。