2025年【React】1417- 基于 useEffect 封装高阶 hook API

【React】1417- 基于 useEffect 封装高阶 hook API前言 useEffect 通常用于状态更新导致的副作用 useEffect effect deps 它有两个参数 effect effect 是一个表示副作用的函数 组件每渲染一次 该函数就自动执行一次 deps 有时候 我们不希望 useEffect

大家好,我是讯享网,很高兴认识大家。

前言

useEffect 通常用于状态更新导致的副作用。

useEffect(effect, deps);

讯享网

它有两个参数:

effect

effect 是一个表示副作用的函数,组件每渲染一次,该函数就自动执行一次。

deps

有时候,我们不希望 useEffect() 每次渲染都执行,这时可以使用它的第二个参数 deps,使用一个数组指定副作用函数的依赖项,只有依赖项发生变化,才会重新渲染。


本文主要分享一些基于 useEffect 封装的高阶 effect hook。

简单的封装

先来一个最简单的例子:只运行一次 effect 的 useEffectOnce

讯享网function useEffectOnece(effect) {   useEffect(effect, []); // deps 为空 }

在组件生命周期中,挂载卸载的生命周期函数也只会执行一次,因此我们可以基于 useEffectOnce 来实现 useMountuseUnmount

function useMount(fn) {   useEffectOnce(() => {     fn();   }); };
讯享网function useUnmount(fn) {   const fnRef = useRef(fn);   fnRef.current = fn; // 始终保证是最新的函数   useEffectOnce(() => () => fnRef.current()); };

useAsyncEffect

useEffect 本身不支持 async 函数,我们不能这样写:

useEffect(async () => {    await getData(); }, []);

理由是 effect 函数应该返回一个销毁函数,如果 useEffect 第一个参数传入 async 函数,返回值则变成了 Promise,会导致 react 在调用销毁函数的时候报错:function.apply is undefined

我们可以自己改造一下实现 useAsyncEffect,让 useEffect 支持 async 函数。

普通版

讯享网function useAsyncEffect(effect, deps) {   useEffect(() => {     const e = effect();     async function execute() {       await e;     }     execute();   }, deps); }

高级版:需要判断 generator 场景

function useAsyncEffect(effect, deps) {   // 判断是否是生成器   function isGenerator(val) {     return typeof val[Symbol.asyncIterator] === 'function';   }      useEffect(() => {     const e = effect();     let cancelled = false;          async function execute() {       // 增加生成器判断分支       if (isGenerator(e)) {         // 无限迭代,直到拿到生成器最终结果         while (true) {           const result = await e.next();           // 迭代过程中,组件已经卸载或者迭代完成,则不再继续           if (cancelled || result.done) {             break;           }         }       } else {         await e;       }     }     execute();          return () => {       cancelled = true;     };   }, deps); }

useUpdateEffect

有些场景我们不想在首次渲染时就执行 effect。比如搜索时,只在 keyword 变化时才调用 search 方法,我们可以封装 useUpdateEffect,它会忽略 useEffect 首次执行,只在依赖更新时执行。

讯享网// 用于记录当前渲染是否是首次渲染 function useFirstMountState() {   const isFirst = useRef(true);   if (isFirst.current) {     isFirst.current = false;     return true;   }   return isFirst.current; } function useUpdateEffect(effect, deps) {   const isFirstMount = useFirstMountState(); // 是否首次渲染   useEffect(() => {     if (!isFirstMount) { // 如果不是首次,则执行 effect 函数       return effect();     }   }, deps); };

useCustomCompareEffect

useEffect 的 deps 比较采用的是 Object.is 方法,比较的是值或引用是否完全一致。

function areHookInputsEqual(   nextDeps: Array<mixed>,   prevDeps: Array<mixed> | null, ) {   if (prevDeps === null) {     return false;   }      for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {     if (is(nextDeps[i], prevDeps[i])) {       continue;     }     return false;   }      return true; }

我们可以为 useEffect 增加自定义比较 deps 的能力:

讯享网function useCustomCompareEffect(effect, deps, depsEqual) {   const ref = useRef(undefined); // 手动维护 deps      // 用自定义方法判断不相同,才修改 deps   if (!ref.current || !depsEqual(deps, ref.current)) {     ref.current = deps;   }   useEffect(effect, ref.current); };

继续基于 useCustomCompareEffect 封装两种常用的浅比较深比较 effect。

useShallowCompareEffect

采用浅比较的方式比较 deps

import { equal as isShallowEqual } from 'fast-shallow-equal'; // 浅比较 const shallowEqualDepsList = (prevDeps, nextDeps) =>   prevDeps.every((dep, index) => isShallowEqual(dep, nextDeps[index]));    function useShallowCompareEffect(effect, deps) {   useCustomCompareEffect(effect, deps, shallowEqualDepsList); };

useDeepCompareEffect

采用深比较的方式比较 deps

讯享网import isDeepEqual from 'fast-deep-equal/react'; function useDeepCompareEffect(effect, deps) {   useCustomCompareEffect(effect, deps, isDeepEqual); };

节流防抖思想也可以用于 useEffect:

useDebounceEffect

为 useEffect 增加防抖的能力

主要思路:

  1. deps 变化,正常触发 effect,同时防抖开始计时
  2. deps 频繁变化,进行防抖处理,因此需要 flag 记录延迟是否结束
  3. 组件卸载后,取消防抖函数调用


    讯享网

// 用来处理防抖函数的 Hook function useDebounceFn(fn, options) {   // 保证 debounce 中每次取到的 fn 都是最新的   const fnRef = useRef(fn);   fnRef.current = fn;   const wait = options?.wait ?? 1000;   const debounced = useMemo(     () =>       debounce(         ((...args) => {           return fnRef.current(...args);         }),         wait,         options,       ),     [],   );   useUnmount(() => {     debounced.cancel();    });   return {     run: debounced,     cancel: debounced.cancel,     flush: debounced.flush,   }; } function useDebounceEffect(effect, deps, debounceOptions) {   const [flag, setFlag] = useState({}); // 记录延迟是否结束   const { run, cancel } = useDebounceFn(() => {     setFlag({});   }, debounceOptions);      // 正常 deps 变化,执行 effect,同时防抖开始计时   useEffect(() => {     return run();   }, deps);      // 组件卸载后,取消防抖函数调用   useUnmount(cancel);      // 使用 useUpdateEffect, 只在 flag 变化时执行 effect   useUpdateEffect(effect, [flag]); }

useThrottleEffect

为 useEffect 增加节流的能力

讯享网// `useThrottleEffect` 与 `useDebounceEffect` 类似,这里不再陈述 function useThrottleEffect(effect, deps, throttleOptions) {   const [flag, setFlag] = useState({});      const { run, cancel } = useThrottleFn(() => {     setFlag({});   }, options);   useEffect(() => {     return run();   }, deps);   useUnmount(cancel);   useUpdateEffect(effect, [flag]); }

结语

以上实现参考 react-use[1] 和 ahooks[2]。我们平时应该多使用这类工具 hook 来提效开发。

如果本文对你有帮助,帮我点个赞吧。

关注我,一起变强!🚀

参考资料

[1]

react-use: https://github.com/streamich/react-use/

[2]

ahooks: https://github.com/alibaba/hooks

往期回顾

#

11 个需要避免的 React 错误用法

#

6 个 Vue3 开发必备的 VSCode 插件

#

3 款非常实用的 Node.js 版本管理工具

#

6 个你必须明白 Vue3 的 ref 和 reactive 问题

#

6 个意想不到的 JavaScript 问题

#

3 分钟掌握 Node.js 版本的区别

#

试着换个角度理解低代码平台设计的本质

ef95de3ed3756ea39599ef2b4434757b.gif

小讯
上一篇 2025-03-16 23:48
下一篇 2025-03-31 20:52

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/125482.html