react-use 中的 useRafLoop:在 RAF 循环中执行回调且不触发父组件重渲染的完整指南

发布时间:2026/9/19 19:59:34

react-use 中的 useRafLoop:在 RAF 循环中执行回调且不触发父组件重渲染的完整指南 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),仅供参考
延伸阅读

更多相关文章

2026/9/19 19:59:34

Flutter鸿蒙开发实战:从环境搭建到hap打包上架全流程

你有没有过这种时刻:朋友圈编辑框打开,光标闪了半天,硬是憋不出一句像样的话。我身边的运营同事、做微商的朋友,甚至我自己偶尔想发条有质感的动态,都得在相册和备忘录之间来回折腾。正好手头接到一个小需求&#xff0…

2026/9/19 19:59:34

现代智能雷达技术3——概述(2)

雷达检测理论核心是在噪声中准确识别目标,面临虚警与漏检的权衡。经典NP准则在已知噪声分布时最大化检测概率;现实中的未知杂波参数催生GLRT思想,奠定自适应检测基础。工程上,CFAR技术动态调整门限:CA-CFAR适用于均匀背…

2026/9/20 5:25:02

npm install 深度解析:依赖安装背后的契约执行与拓扑求解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 5:25:02

3分钟跑通多目标追踪:BoxMOT 一站式追踪器装配工具

3分钟跑通多目标追踪:BoxMOT 一站式追踪器装配工具 【免费下载链接】boxmot BoxMOT: Pluggable Python and C SOTA multi-object tracking modules with support for axis-aligned and oriented bounding boxes 项目地址: https://gitcode.com/GitHub_Trending/bo…

2026/9/20 5:20:02

MMPose 姿态估计上手:从安装到跑出第一个结果有多远?

MMPose 姿态估计上手:从安装到跑出第一个结果有多远? 【免费下载链接】mmpose OpenMMLab Pose Estimation Toolbox and Benchmark. 项目地址: https://gitcode.com/GitHub_Trending/mm/mmpose MMPose 是 OpenMMLab 开源的姿态估计工具箱&#xff…

2026/9/20 0:04:49

GAMP 5 基于风险的计算机化系统验证:软件分类与审计追踪实践

简介:《A Risk-Based Approach to Compliant GxP Computerized Systems》即业内熟知的GAMP 5指南,面向制药企业质量与IT合规人员、验证工程师及计算机化系统管理者,用于解决GxP法规环境下系统合规性难以科学落地的问题。文档以风险管理为主线…

2026/9/20 0:04:49

安全托管MSSP实战:从静态防御到人机协同的攻防运营与应急响应

简介:这份PPT围绕互联网业务安全托管服务展开,面向企业安全负责人、IT运维人员及关注MSSP/MSS选型的读者,重点回应传统安全过度依赖人工、碎片化静态防御难以对抗产业化攻击等痛点。资源共1个pptx文件,包体约30.63MB,以…

2026/9/20 0:04:49

GAMP 5 基于风险的计算机化系统验证:软件分类与审计追踪实践

简介:《A Risk-Based Approach to Compliant GxP Computerized Systems》即业内熟知的GAMP 5指南,面向制药企业质量与IT合规人员、验证工程师及计算机化系统管理者,用于解决GxP法规环境下系统合规性难以科学落地的问题。文档以风险管理为主线…

2026/9/20 0:04:49

安全托管MSSP实战:从静态防御到人机协同的攻防运营与应急响应

简介:这份PPT围绕互联网业务安全托管服务展开,面向企业安全负责人、IT运维人员及关注MSSP/MSS选型的读者,重点回应传统安全过度依赖人工、碎片化静态防御难以对抗产业化攻击等痛点。资源共1个pptx文件,包体约30.63MB,以…

2026/9/20 4:54:47

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/20 5:01:23

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/20 5:09:33

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码