react-use 之 useEffectOnce:在 React 中只执行一次 Effect 的生命周期 Hook 全解析
react-use 之 useEffectOnce在 React 中只执行一次 Effect 的生命周期 Hook 全解析【免费下载链接】react-useReact Hooks — 项目地址: https://gitcode.com/gh_mirrors/re/react-useuseEffectOnce是 react-use 提供的一个生命周期 Hook它把 React 原生的useEffect包装为仅在组件挂载时执行一次、并在卸载时执行清理的语义。在 react-use 项目中它是useMount、useUnmount等生命周期 Hook 的底层基石适合需要精确控制初始化逻辑与副作用清理的场景。读完本文你将掌握useEffectOnce的源码实现、完整用法、测试验证方式以及它与其他生命周期 Hook 的关联与取舍。为什么需要useEffectOnceReact 原生的useEffect默认在每次渲染后都会执行副作用开发者通常通过依赖数组来控制执行时机。但当你想表达只在组件首次挂载时运行一次这一明确意图时直接写useEffect(fn, [])虽然可行却存在两个问题意图不清晰空依赖数组的写法让读者需要揣测这里为什么是空数组而useEffectOnce从命名上直接声明了只执行一次的语义容易误改后续维护者可能无意间向依赖数组中加入依赖从而破坏仅一次的约定。将逻辑封装为独立的 Hook 可以从 API 层面杜绝这类误用。react-use 在 README.md 中将其描述为modifieduseEffecthook that only runs once经过改造的、只运行一次的useEffect可见它的定位就是useEffect的语义化封装。源码实现仅五行的极简封装useEffectOnce的实现位于 src/useEffectOnce.ts完整代码如下import { EffectCallback, useEffect } from react; const useEffectOnce (effect: EffectCallback) { useEffect(effect, []); }; export default useEffectOnce;核心逻辑只有一行useEffect(effect, [])。这意味着传入的effect回调被原样交给 React 的useEffect依赖数组固定为[]空数组React 保证该 effect 只在组件首次挂载后执行一次如果effect返回一个清理函数cleanup function该函数会在组件卸载时被 React 自动调用。参数类型EffectCallback直接来自types/react当前仓库使用types/react17.0.0见 package.json它等价于() void | Destructor即无参函数可选返回清理函数。从源码结构看这个 Hook 刻意不接收任何配置项、不支持依赖数组API 面被压缩到最小——这正是它的设计哲学把仅一次这一单一职责封装干净。基本用法在组件中通过命名导入即可使用import {useEffectOnce} from react-use; const Demo () { useEffectOnce(() { console.log(Running effect once on mount) return () { console.log(Running clean-up of effect on unmount) } }); return null; };运行效果组件挂载后控制台输出Running effect once on mount且无论组件如何重渲染都不会再次输出组件卸载时控制台输出Running clean-up of effect on unmount用于释放事件监听、清除定时器、取消订阅等资源。该 Hook 已在 src/index.ts 中作为具名导出对外提供因此可以直接从react-use包导入无需关心内部路径。API 签名useEffectOnce(effect: EffectCallback);参数类型说明effectEffectCallback即() void \| Destructor组件挂载后执行的回调可返回一个清理函数该函数会在组件卸载时被调用返回值无void。测试验证如何证明只执行一次仓库在 tests/useEffectOnce.test.ts 中给出了针对该 Hook 的完整测试使用testing-library/react-hooks的renderHook驱动是理解其行为契约的最佳参考import { renderHook } from testing-library/react-hooks; import { useEffectOnce } from ../src; const mockEffectCleanup jest.fn(); const mockEffectCallback jest.fn().mockReturnValue(mockEffectCleanup); it(should run provided effect only once, () { const { rerender } renderHook(() useEffectOnce(mockEffectCallback)); expect(mockEffectCallback).toHaveBeenCalledTimes(1); rerender(); expect(mockEffectCallback).toHaveBeenCalledTimes(1); }); it(should run clean-up provided on unmount, () { const { unmount } renderHook(() useEffectOnce(mockEffectCallback)); expect(mockEffectCleanup).not.toHaveBeenCalled(); unmount(); expect(mockEffectCleanup).toHaveBeenCalledTimes(1); });这两个用例分别锁定了该 Hook 的两条核心契约effect 只执行一次首次渲染后回调被调用 1 次触发rerender()后依然只调用 1 次证明空依赖数组确实阻止了重复执行清理函数在卸载时执行卸载前清理函数从未被调用unmount()后恰好调用 1 次证明清理逻辑的生命周期边界正确。与相关生命周期 Hook 的关系在 react-use 中useEffectOnce并非孤立存在它是多个生命周期 Hook 的底层实现useMount内部直接调用useEffectOnce实现挂载后调用一次回调import useEffectOnce from ./useEffectOnce; const useMount (fn: () void) { useEffectOnce(() { fn(); }); };区别在于useMount的回调不接收清理函数的返回语义其类型为() void且useMount不会执行卸载逻辑对应的文档见 docs/useMount.md。useUnmount同样基于useEffectOnce但借助useRef始终保存最新的回调确保组件卸载时调用的是最新一次渲染时传入的卸载函数const useUnmount (fn: () any): void { const fnRef useRef(fn); fnRef.current fn; useEffectOnce(() () fnRef.current()); };可以看到它把useEffectOnce的清理函数作为桥梁将卸载回调挂在了 React 的卸载时机上对应文档见 docs/useUnmount.md。useLifecycles功能上等价于useMountuseUnmount的组合接受(mount, unmount?)两个回调内部同样使用空依赖数组的useEffect。useUpdateEffect与useEffectOnce形成互补——它通过useFirstMountState跳过首次挂载只在更新时执行 effect是仅在依赖变化且非首次执行的语义化封装。由此可见useEffectOnce是 react-use 生命周期体系中仅执行一次这一语义的最小公因子向上支撑了useMount/useUnmount等更上层的 Hook向下则直接复用了 React 官方的useEffect能力。使用注意事项不要在其中引用会变化的 props/state由于依赖数组固定为空effect 闭包捕获的是首次渲染时的值之后更新的 props/state 不会触发重新执行。若需要在更新时响应变化应改用useUpdateEffect或显式声明依赖的useEffect。清理函数至关重要在useEffectOnce中订阅事件、开启定时器或发起异步任务时务必返回清理函数否则组件卸载后会产生内存泄漏或对已卸载组件进行状态更新。这也是该 Hook 与useMount的关键差异——useMount无法携带清理逻辑而useEffectOnce完整支持。React 18 StrictMode 下的行为当前仓库基于 React 17.0.2 开发与测试见 package.jsonuseEffectOnce的仅一次语义在 React 17 下严格成立。若项目升级到 React 18 并开启 StrictMode开发模式下 effect 会被 React 额外执行一次挂载→清理→再挂载这是 React 官方为暴露副作用问题而设计的调试行为属于框架层面变化而非该 Hook 本身的缺陷生产构建不受影响。语义优先于手写空依赖即便useEffect(fn, [])与useEffectOnce(fn)行为等价仍建议优先使用useEffectOnce——它让代码意图自文档化也避免后续维护时被误加依赖而破坏语义。小结useEffectOnce用最小的实现成本一行useEffect(effect, [])为 React 应用提供了清晰、可测试、可组合的一次性副作用抽象。理解它的源码、测试契约及其在 react-use 生命周期 Hook 体系中的位置能帮助你在初始化数据拉取、事件订阅、埋点上报等仅需一次的场景中写出意图明确且易于维护的代码。若需同时处理挂载与卸载可进一步查阅 useLifecycles、useMount 与 useUnmount 的源码与对应文档。【免费下载链接】react-useReact Hooks — 项目地址: https://gitcode.com/gh_mirrors/re/react-use创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →