彻底搞懂ahooks原理

前言

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

HookuseEffect实现防抖/节流的能力,并且我们也只讲述防抖。

防抖源码如下:

代码量其实不多,而代码实现的很巧妙,你可能一开始没看懂flag是干嘛的,可以理解是一个触发标识,options中有防抖的参数,如果设置防抖1s,那就会在1s防抖的情况触发setFlag,也就会走到useUpdateEffect中,从而实现防抖的效果,那useEffect是用来干嘛的呢?确保业务侧的依赖状态变化立即执行,不会影响页面。

因此总结下来是这样的:

  • 创建一个防抖执行完成的状态(flag);
  • 将这个状态(flag)作为副作用的触发条件;
  • 保证原依赖项deps能在正常时机触发;

useDeepCompareEffect

Hook用法与useEffect一致,但在deps更新时会通过react-fast-compare来深比较。

源码如下:

也是调用了一个通用的创建深比较副作用的函数,我们再看下createDeepCompareEffect函数是怎么实现的。

这里一共有2个refref用于保存上一次的依赖项数据,而signalRef用于标识是否更新,如果深层比较通过了则触发副作用。

我们看下depsEqual是怎么实现的。

直接调用的react-fast-compare来实现的,那没问题。

useDeepCompareLayoutEffect

同样用了createDeepCompareEffect,只是副作用使用了useLayoutEffect

useInterval

这个属于在业务中非常常用的Hook了,并且ahooks中实现的也很巧妙也很易懂。

源码如下:

这里有几个部分:

  • 创建一个timer实例,用于创建定时器;
  • 缓存传入的定时器回调函数,算是性能优化和函数实例唯一;
  • 创建副作用清除函数,删除定时器;
  • 核心链路:delay类型校验、immediate立即执行判断校验;

useRefInterval

requestAnimationFrame 模拟实现 setInterval,API 和 useInterval 保持一致,好处是可以在页面不渲染的时候停止执行定时器,比如页面隐藏或最小化等。

请注意,如下两种情况下很可能是不适用的,优先考虑 useInterval

  • 时间间隔小于 16ms
  • 希望页面不渲染的情况下依然执行定时器

源码如下:

可以看到对于整个Hook的结构和useInterval基本一致,只是将setIntervalclearInterval单独封装了,我们先看一下setRafInterval函数。

该函数主要做了这几件事:

  • 判断环境,是否支持requestAnimationFrame api,如果不支持则兜底使用setInterval
  • 通过传入的delay来定时调用回调函数;
  • 返回定时器实例;

再看一下clearRafInterval函数的清除逻辑:

该函数主要做了这几件事:

  • 如果用的是setInterval则通过clearInterval清除;
  • 否则用cancelAnimationFrame来清除;

useTimeout

HookuseInterval类似,只是调用了setTimeout,不再过多讲解,源码如下:

useRafTimeout

HookuseTimeout也几乎一致,唯一的区别在setRafTimeout的实现,setRafInterval中会在每次回调后更新start,从而实现定时执行的效果,而useRafTimeout只需要到了时间调用一次即可,因此没有这一步的逻辑,如下:

其他实现和逻辑结构都一致。

useLockFn

Hook用于给一个异步函数增加竞态锁,防止并发执行。简单理解就是如果现在有一个submit的异步函数,当该函数还在执行中,则后续的函数触发都被忽略,通用于表单提交防止多次点击的情况,相信在业务逻辑中前端同学也都写过类似的代码,useLockFn源码如下:

useLockFn实现比较简单,有一个lockRef的控制器,当函数开始执行到结束前都会上锁,把后续的函数调用都省略。

useUpdate

Hook用于更新页面状态,其实就是在useUpdate中维护一个状态,当需要更新时把这个状态更新一下即可,比较类似useDebounceEffect在防抖执行完的副作用操作。

源码如下:

结尾

如有理解错误的地方欢迎指出,本专栏会定期更新,最后讲解完所有Hook,欢迎关注。

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

相关推荐
清灵xmf11 分钟前
深入解析 JavaScript 事件委托
前端·javascript·html·事件委托
小妖别跑41 分钟前
PDA(程序派生地址,Program Derived Address),为什么有这个地址,而不是直接指定地址
前端·智能合约
2301_796982141 小时前
网页打开时,下载的文件text/html/重定向类型有什么作用?
前端·html
重生之我在20年代敲代码1 小时前
HTML讲解(二)head部分
前端·笔记·html·web app
天下无贼!1 小时前
2024年最新版TypeScript学习笔记——泛型、接口、枚举、自定义类型等知识点
前端·javascript·vue.js·笔记·学习·typescript·html
小白小白从不日白2 小时前
react 高阶组件
前端·javascript·react.js
程序员大金2 小时前
基于SpringBoot+Vue+MySQL的智能物流管理系统
java·javascript·vue.js·spring boot·后端·mysql·mybatis
Mingyueyixi2 小时前
Flutter Spacer引发的The ParentDataWidget Expanded(flex: 1) 惨案
前端·flutter
Rverdoser3 小时前
unocss 一直热更新打印[vite] hot updated: /__uno.css
前端·css