前言
ahooks是优秀的React Hooks库,也是我们日常中开发一直会用到的库,学习理解这些Hooks的源码可以让我们深入理解ahooks,也能让我们对React基础Hook的理解更上一层楼。
本文我们将对ahooks中所有副作用的钩子函数进行源码剖析。
useUpdateEffect
看了下代码,直接调用了createUpdateEffect函数,不难猜测这是个公共函数,为什么要把useEffect传进去?不难想象到接下来也会有一个传入useLayoutEffect的场景。
再到函数这个创建副作用的函数里去看逻辑:
一共两个副作用:
- 副作用1:在组件销毁重置
isMounted状态; - 副作用2:通过
isMounted来判断是否是首次副作用执行,从而达到效果;
useUpdateLayoutEffect
和上面说的一样,通用化了createUpdateEffect函数,只是副作用触发时机不同,调用的是useLayoutEffect
useAysncEffect
让useEffect中支持异步函数的执行,如果你在useEffect的函数采用异步的形式,React会报错,源码如下:
useAsyncEffect的入参与useEffect完全一致,核心思路是传入执行的函数,这里可以传入generator函数或异步函数,同时官方文档称【在副作用销毁会停止函数执行节约性能】
- 如果是
generator函数,会直接走完所有的yield,在每一次执行时,会检查是否取消------cancelled字段,而当副作用销毁时cancelled字段会被标为已取消,因此就不会继续往下走,这是主要的实现原理; - 如果是
promise函数,则比较简单,直接执行即可。
useDounceFn & useThrottleFn
由于实现原理一致,都是调用了lodash的防抖节流方法,因此这两个Hook我们放在一起讲,只讲防抖。
该函数是用来处理防抖的Hook,源码如下:
一共做了以下几件事情:
- 参数校验,对函数类型的入参进行异常校验;
- 创建函数实例;
- 创建防抖时间,默认1秒;
- 将参数传入
doubounce工具函数,返回一个对象,包含了执行函数、取消函数和立即调用函数;
接下来我们看下doubonce的实现。
直接调用了lodash库中的防抖函数,那没问题了。
useDebounceEffect & useThrottleEffect
该Hook为useEffect实现防抖/节流的能力,并且我们也只讲述防抖。
防抖源码如下:
代码量其实不多,而代码实现的很巧妙,你可能一开始没看懂flag是干嘛的,可以理解是一个触发标识,options中有防抖的参数,如果设置防抖1s,那就会在1s防抖的情况触发setFlag,也就会走到useUpdateEffect中,从而实现防抖的效果,那useEffect是用来干嘛的呢?确保业务侧的依赖状态变化立即执行,不会影响页面。
因此总结下来是这样的:
- 创建一个防抖执行完成的状态(flag);
- 将这个状态(flag)作为副作用的触发条件;
- 保证原依赖项deps能在正常时机触发;
useDeepCompareEffect
该Hook用法与useEffect一致,但在deps更新时会通过react-fast-compare来深比较。
源码如下:
也是调用了一个通用的创建深比较副作用的函数,我们再看下createDeepCompareEffect函数是怎么实现的。
这里一共有2个ref,ref用于保存上一次的依赖项数据,而signalRef用于标识是否更新,如果深层比较通过了则触发副作用。
我们看下depsEqual是怎么实现的。
直接调用的react-fast-compare来实现的,那没问题。
useDeepCompareLayoutEffect
同样用了createDeepCompareEffect,只是副作用使用了useLayoutEffect。
useInterval
这个属于在业务中非常常用的Hook了,并且ahooks中实现的也很巧妙也很易懂。
源码如下:
这里有几个部分:
- 创建一个timer实例,用于创建定时器;
- 缓存传入的定时器回调函数,算是性能优化和函数实例唯一;
- 创建副作用清除函数,删除定时器;
- 核心链路:
delay类型校验、immediate立即执行判断校验;
useRefInterval
用 requestAnimationFrame 模拟实现 setInterval,API 和 useInterval 保持一致,好处是可以在页面不渲染的时候停止执行定时器,比如页面隐藏或最小化等。
请注意,如下两种情况下很可能是不适用的,优先考虑 useInterval :
- 时间间隔小于
16ms - 希望页面不渲染的情况下依然执行定时器
源码如下:
可以看到对于整个Hook的结构和useInterval基本一致,只是将setInterval、clearInterval单独封装了,我们先看一下setRafInterval函数。
该函数主要做了这几件事:
- 判断环境,是否支持
requestAnimationFrame api,如果不支持则兜底使用setInterval; - 通过传入的
delay来定时调用回调函数; - 返回定时器实例;
再看一下clearRafInterval函数的清除逻辑:
该函数主要做了这几件事:
- 如果用的是
setInterval则通过clearInterval清除; - 否则用
cancelAnimationFrame来清除;
useTimeout
该Hook和useInterval类似,只是调用了setTimeout,不再过多讲解,源码如下:
useRafTimeout
该Hook和useTimeout也几乎一致,唯一的区别在setRafTimeout的实现,setRafInterval中会在每次回调后更新start,从而实现定时执行的效果,而useRafTimeout只需要到了时间调用一次即可,因此没有这一步的逻辑,如下:
其他实现和逻辑结构都一致。
useLockFn
该Hook用于给一个异步函数增加竞态锁,防止并发执行。简单理解就是如果现在有一个submit的异步函数,当该函数还在执行中,则后续的函数触发都被忽略,通用于表单提交防止多次点击的情况,相信在业务逻辑中前端同学也都写过类似的代码,useLockFn源码如下:
useLockFn实现比较简单,有一个lockRef的控制器,当函数开始执行到结束前都会上锁,把后续的函数调用都省略。
useUpdate
该Hook用于更新页面状态,其实就是在useUpdate中维护一个状态,当需要更新时把这个状态更新一下即可,比较类似useDebounceEffect在防抖执行完的副作用操作。
源码如下:
结尾
如有理解错误的地方欢迎指出,本专栏会定期更新,最后讲解完所有Hook,欢迎关注。
如果喜欢我的文章或者想上岸大厂,可以关注公众号「量子前端」,将不定期关注推送前端好文、分享就业资料秘籍,也希望有机会一对一帮助你实现梦想。