恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
react-use 中的 useRafLoop:在 RAF 循环中执行回调且不触发父组件重渲染的完整指南
首页
资讯中心
/
react-use 中的 useRafLoop:在 RAF 循环中执行回调且不触发父组件重渲染的完整指南
react-use 中的 useRafLoop:在 RAF 循环中执行回调且不触发父组件重渲染的完整指南
发布时间:2026/9/19 19:59:15
react-use 中的 useRafLoop在 RAF 循环中执行回调且不触发父组件重渲染的完整指南【免费下载链接】react-useReact Hooks — 项目地址: https://gitcode.com/gh_mirrors/re/react-use导读useRafLoop是 react-use 提供的一个副作用Side-effect类 Hook它允许你在浏览器requestAnimationFrameRAF循环中持续调用给定回调函数同时不会因为 Hook 自身状态的变化而重渲染父组件。它适用于帧动画、实时计数、性能监控等需要与浏览器刷新率同步的场景并内置了循环停止/启动控制和状态查询方法。读完本文你将掌握useRafLoop的完整 API、参数语义、底层实现原理、测试验证方式以及如何把它应用到真实组件中。一、useRafLoop 是什么从官方文档的定义来看这个 Hook 的核心承诺有三点在 RAF 循环中调用给定函数——回调会跟随浏览器每一帧的刷新节奏被调用不重渲染父组件——循环的调度状态存放在 ref 中不经过 React 的 state 系统因此不会引发多余的渲染组件卸载时循环自动停止——通过useEffect的清理函数在卸载时取消动画帧避免内存泄漏与后台空转。此外Hook 还会返回一组方法用于手动启动/停止循环以及查询当前循环是否处于活动状态。这在需要用户交互控制如开始/暂停按钮或按条件开启动画的场景中非常有用。二、快速上手一个带开始/停止按钮的 RAF 计数器文档给出的典型示例结合了useUpdate来强制刷新 UI。我们直接复现并拆解它import * as React from react; import { useRafLoop, useUpdate } from react-use; const Demo () { const [ticks, setTicks] React.useState(0); const [lastCall, setLastCall] React.useState(0); const update useUpdate(); const [loopStop, loopStart, isActive] useRafLoop((time) { setTicks(ticks ticks 1); setLastCall(time); }); return ( div divRAF triggered: {ticks} (times)/div divLast high res timestamp: {lastCall}/div br / button onClick{() { isActive() ? loopStop() : loopStart(); update(); }}{isActive() ? STOP : START}/button /div ); };这个示例揭示了几个关键使用模式回调参数time是 DOMHighResTimeStamp即自 time origin 以来经过的毫秒数高精度时间戳可以直接展示最近一次回调的时间setTicks使用函数式更新setTicks(ticks ticks 1)避免闭包捕获过期的ticks值这是在高频回调里更新状态的推荐写法update()用于同步按钮文案useRafLoop本身不会触发重渲染所以切换启停状态后需要调用 useUpdate内部基于useReducer实现强制渲染来让按钮的 STOP/START 文案即时刷新。useUpdate的实现见 src/useUpdate.ts。这个 Demo 也完整存在于仓库的 Storybook 中见 stories/useRafLoop.story.tsx。三、完整 API 参考文档的 Reference 部分给出了完整签名const [stopLoop, startLoop, isActive] useRafLoop(callback: FrameRequestCallback, initiallyActive true);参数参数类型说明callback(time: number) void每一帧 RAF 触发时被调用的函数time为DOMHighResTimeStamp表示自 time origin 以来的毫秒数高精度时间initiallyActiveboolean是否在首次渲染时就启动循环默认值为true返回值返回一个长度固定为 3 的元组[stopLoop, startLoop, isActive]返回值类型说明stopLoop() void如果循环处于活动状态则停止它startLoop() void如果循环处于非活动状态则启动它isActive() boolean循环当前是否处于活动状态活动时返回true在源码中返回类型被明确声明为RafLoopReturns [() void, () void, () boolean]见 src/useRafLoop.ts。四、底层实现原理结合 src/useRafLoop.ts 的完整源码我们可以看清它的内部机制1. 三个 ref 承担调度核心const raf useRefnumber | null(null); // 当前 RAF 请求的 ID const rafActivity useRefboolean(false); // 循环活动状态标志 const rafCallback useRef(callback); // 最新回调的引用 rafCallback.current callback;raf保存requestAnimationFrame返回的请求 ID用于后续cancelAnimationFramerafActivity是循环是否活动的布尔开关它不经过 React 状态系统这正是不重渲染父组件的根本原因rafCallback在每次渲染时被更新为最新的callback。这意味着即使父组件因其他原因重新渲染并传入了新的回调正在进行的循环也会立即使用最新回调而无需重启循环——测试 tests/useRafLoop.test.tsx 专门验证了这一点。2. step循环的每一帧const step useCallback((time: number) { if (rafActivity.current) { rafCallback.current(time); raf.current requestAnimationFrame(step); } }, []);step是单帧执行体先检查活动标志再调用回调最后递归地注册下一帧。这种回调执行后再申请下一帧的方式保证了每一帧之间严格串行回调的执行时间也会被计算进帧间隔内。3. start / stop / isActive 三方法const result useMemo( () [ () { // stop if (rafActivity.current) { rafActivity.current false; raf.current cancelAnimationFrame(raf.current); } }, () { // start if (!rafActivity.current) { rafActivity.current true; raf.current requestAnimationFrame(step); } }, (): boolean rafActivity.current, // isActive ] as RafLoopReturns, [] );三个方法都是幂等的stop只在活动时取消当前帧start只在非活动时申请新帧isActive直接返回标志位。方法整体用useMemo(..., [])包装因此在整个组件生命周期内引用保持稳定可以放心放进依赖数组或传给子组件。4. 挂载启动与卸载清理useEffect(() { if (initiallyActive) { result[1](); // start } return result[0]; // stop清理函数 }, []);挂载时若initiallyActive为真则立即启动循环卸载时返回的清理函数会调用stopcancelAnimationFrame取消未完成的帧请求循环自动停止不会发生内存泄漏。测试 tests/useRafLoop.test.tsx 通过hook.unmount()验证了卸载后回调不再被调用。五、行为验证测试用例告诉我们什么仓库为useRafLoop编写了完整的单元测试使用raf-stub替换真实的requestAnimationFrame以模拟帧推进见 tests/useRafLoop.test.tsx。这些测试精确刻画了 Hook 的契约行为测试用例验证的行为should be definedHook 导出存在should return object with start, stop and isActive functions返回值是包含三个函数的元组should constantly call callback inside the raf loop每step(2)两帧回调被调用 2 次循环持续进行should not start the loop if 2nd hook parameter is falsyinitiallyActive false时循环不会自动启动should pass the time argument to given callback回调收到number类型的时间参数should stop the loop on component unmount卸载后回调不再被调用should call the actual callback when it changed回调变化后循环使用最新的回调stop/start/isActive 相关用例三个方法的行为及幂等性其中isActive的验证路径尤其清晰isActive()在start()后为truestop()后为falsetests/useRafLoop.test.tsx与文档描述完全一致。六、与其他 RAF 相关 Hook 的对比与选型react-use 中还有另外两个基于 RAF 的 Hook理解它们的差异有助于正确选型useRafLoop持续调用你的回调但不管理任何状态、不重渲染父组件适合需要自己掌控每一帧逻辑的场景useRaf内部维护一个elapsed数值状态0 到 1 的进度每次帧更新都会触发组件重渲染适合做进度条、动画时长归一化等需要状态驱动 UI的场景实现见 src/useRaf.tsuseRafState把useState的更新调度到 RAF 帧内批量执行用于限制高频状态更新的渲染频率。如果你的诉求是每帧做一件事但不想让组件频繁重渲染useRafLoop是最直接的选择如果你需要每帧的数值驱动渲染则应该考虑useRaf。七、使用注意事项回调中更新状态时使用函数式更新由于callback可能在多帧中被调用直接读取闭包外的状态值容易拿到过期数据推荐setX(x ...)写法切换启停后手动触发渲染stopLoop/startLoop不会引起重渲染若 UI 需要反映启停状态如按钮文案请配合useUpdate()使用后台标签页自动暂停浏览器在标签页不可见时会自动暂停requestAnimationFrame因此useRafLoop天然不会在后台空转消耗 CPUinitiallyActive的默认值默认true即组件挂载即开始循环若希望由用户或条件控制启动时机请传入false并手动调用startLoop导出路径useRafLoop已从入口统一导出可直接import { useRafLoop } from react-use使用见 src/index.ts。八、小结useRafLoop是一个设计精巧、聚焦单一职责的 Hook用三个 ref 撑起整个 RAF 调度把循环控制与React 渲染彻底解耦同时通过useEffect清理机制保证卸载安全。配合 官方文档、源码实现 与 测试用例你可以放心地将它用于帧动画、实时计时、性能监控等与浏览器刷新率同步的实战场景。【免费下载链接】react-useReact Hooks — 项目地址: https://gitcode.com/gh_mirrors/re/react-use创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考