干货 | React Hook的实现原理和最佳实践

本文探讨了React Hook的出现背景,包括React.mixin和高阶组件的局限性,并详细介绍了React Hook的实现原理,通过实例展示了如何使用Hook解决功能复用问题。作者通过模拟实现useState和useEffect来解释Hook的工作机制,并讨论了在生产环境中如何运用Hook,包括模拟生命周期、数据请求、性能优化和实现简版Redux。最后,提出了自定义Hook的最佳实践和一个useImgLazy的Hook概念,鼓励读者探索和创造更多实用的Hook。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者简介

何鸣明,携程境外专车研发部前端工程师,主要负责境外打车、包车业务。喜欢交流分享。

一、在谈 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的工作方式困惑,不必在不同生命周期中处理业务。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值