前言
useEffect 通常用于状态更新导致的副作用。
useEffect(effect, deps);
讯享网
它有两个参数:
effect
effect 是一个表示副作用的函数,组件每渲染一次,该函数就自动执行一次。
deps
有时候,我们不希望 useEffect() 每次渲染都执行,这时可以使用它的第二个参数 deps,使用一个数组指定副作用函数的依赖项,只有依赖项发生变化,才会重新渲染。
本文主要分享一些基于 useEffect 封装的高阶 effect hook。
简单的封装
先来一个最简单的例子:只运行一次 effect 的 useEffectOnce:
讯享网function useEffectOnece(effect) { useEffect(effect, []); // deps 为空 }
在组件生命周期中,挂载和卸载的生命周期函数也只会执行一次,因此我们可以基于 useEffectOnce 来实现 useMount 和 useUnmount。
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增加防抖的能力
主要思路:
- deps 变化,正常触发 effect,同时防抖开始计时
- deps 频繁变化,进行防抖处理,因此需要
flag记录延迟是否结束 - 组件卸载后,取消防抖函数调用
// 用来处理防抖函数的 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 版本的区别
#
试着换个角度理解低代码平台设计的本质


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