前言
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
,欢迎关注。
如果喜欢我的文章或者想上岸大厂,可以关注公众号「量子前端」,将不定期关注推送前端好文、分享就业资料秘籍,也希望有机会一对一帮助你实现梦想。