
react-native-reanimated useFrameCallback 深度指南逐帧回调的用法、FrameInfo 语义与底层实现原理【免费下载链接】react-native-reanimatedReact Natives Animated library reimplemented项目地址: https://gitcode.com/GitHub_Trending/re/react-native-reanimateduseFrameCallback是 react-native-reanimated 提供的逐帧per-frame回调 Hook自 v2.10.0 起可用。它允许你在每一帧画面刷新时执行一段 worklet 代码是驱动自定义逐帧动画、帧率统计、时间累积计算等场景的核心工具。读完本文你将掌握其完整 API、FrameCallback/FrameInfo对象语义、与useAnimatedStyle的组合用法并能结合源码理解其在 UI 线程上的注册与调度机制。一、useFrameCallback 是什么在 React Native 的动画体系里大多数动画由withTiming、withSpring等动画函数驱动由引擎在内部逐帧推进。而useFrameCallback把“逐帧更新”这件事直接暴露给你它接收一个 worklet 回调函数让这段代码在每一帧画面更新时都被执行一次。useFrameCallback(callback: (frameInfo: FrameInfo) void, autostart true): [FrameCallback]从函数签名可以看出callback一个 worklet 函数每一帧被调用一次接收一个FrameInfo对象作为参数autostart可选布尔值指定回调在注册完成后是否立即开始运行默认为true返回值一个FrameCallback对象用于读取和控制回调的运行状态。该 Hook 的 TypeScript 定义位于 useFrameCallback.ts其 JSDoc 注释明确说明Lets you run a function on every frame update.二、核心类型详解FrameCallback: [object]useFrameCallback返回的对象包含三个属性属性类型说明setActive(isActive: boolean) void开始 / 停止监听帧更新isActiveboolean指示回调当前是否处于激活状态true为激活callbackIdnumber回调函数的唯一标识符该类型定义见 useFrameCallback.ts。FrameInfo: [object]回调函数在每一帧收到的参数对象包含三个属性属性类型说明timestampnumber最后一帧渲染时的系统时间毫秒timeSincePreviousFramenumber \| null距上一帧的时间毫秒。激活后的第一帧该值为null从第二帧开始在 60 Hz 屏幕上约为 16 ms在 120 Hz 屏幕上约为 8 ms无卡顿的情况下timeSinceFirstFramenumber距回调最近一次被激活的时间毫秒该类型定义见 FrameCallbackRegistryUI.ts。关于timeSincePreviousFrame在第一帧为null的行为可以从 UI 线程注册表的实现中印证每个回调都维护一个startTime第一次激活时它被置为null回调收到的帧信息中timeSincePreviousFrame即为null、timeSinceFirstFrame为0从第二帧起才计算真实的帧间隔见下文底层实现一节。三、完整示例逐帧移动方块以下示例完整取自原文档并保留全部细节回调每帧把方块向右移动 1 像素同时打印帧间隔信息通过按钮在激活与停止之间切换。import Animated, { useAnimatedStyle, useFrameCallback, useSharedValue, } from react-native-reanimated; import { Button, StyleSheet, View } from react-native; import React from react; export default function FrameCallbackExample() { const x useSharedValue(0); const frameCallback useFrameCallback((frameInfo) { if (frameInfo.timeSincePreviousFrame null) { console.log(First frame!); } else { console.log( ${frameInfo.timeSincePreviousFrame} ms have passed since the previous frame ); } // Move the box by one pixel on every frame x.value 1; }, false); const animatedStyle useAnimatedStyle(() { return { transform: [ { translateX: x.value, }, ], }; }); return ( View Animated.View style{[styles.box, animatedStyle]} / Button titleStart/stop onPress{() frameCallback.setActive(!frameCallback.isActive)} / /View ); } const styles StyleSheet.create({ box: { width: 100, height: 100, backgroundColor: red, }, });这个示例的关键点在于autostart false回调注册后并不立即运行而是等用户点击按钮通过frameCallback.setActive(true)手动启动——这是按需启动的典型用法第一帧特殊处理timeSincePreviousFrame null被用来识别激活后的第一帧与useAnimatedStyle联动在 UI 线程上逐帧修改共享值x.valueuseAnimatedStyle读取该值驱动Animated.View的translateX构成逐帧动画的完整闭环。四、用法细节与最佳实践4.1 控制启停setActive 与 isActive返回的FrameCallback中setActive(true)/setActive(false)分别开始 / 停止监听帧更新isActive反映当前状态。按钮切换是常见模式onPress{() frameCallback.setActive(!frameCallback.isActive)}4.2 利用 FrameInfo 做时间累积timeSinceFirstFrame从回调被激活起持续累计可用于实现基于真实帧时间的累积逻辑如倒计时、进度条。注意它是以最近一次激活为起点每次setActive(true)重新激活都会重置对应源码中startTime被重置的行为。4.3 用 timeSincePreviousFrame 监控帧率timeSincePreviousFrame在无卡顿时应稳定在约 16 ms60 Hz或 8 ms120 Hz。若该值显著偏大说明发生了掉帧可借此实现帧率监控或自适应降级逻辑。五、底层实现原理源码级useFrameCallback的实现跨越 JS 线程与 UI 线程核心代码分布在三个文件中5.1 JS 侧的注册入口useFrameCallback.ts 负责 Hook 生命周期管理用useRef缓存FrameCallback对象保证每次渲染返回的是同一个引用useEffect中调用frameCallbackRegistry.registerFrameCallback(callback)完成注册并用拿到的callbackId初始化状态注册后立即调用setActive(ref.current.isActive)将autostart的意图同步到 UI 线程清理函数中调用unregisterFrameCallback并在本地把callbackId重置为-1组件卸载即停止并注销回调依赖数组为[callback, autostart]回调函数或autostart变化时重新注册。5.2 JS 注册表跨线程通信FrameCallbackRegistryJS.ts 是一个 JS 侧单例类维护自增的nextCallbackId分配唯一 ID通过scheduleOnUI来自 react-native-worklets把registerFrameCallback、unregisterFrameCallback、manageStateFrameCallback三类操作调度到 UI 线程执行操作对象是global._frameCallbackRegistry若传入的callback为空返回-1作为无效 ID。5.3 UI 线程注册表帧循环调度FrameCallbackRegistryUI.ts 定义了 UI 线程上的注册表与帧循环数据结构frameCallbackRegistryMapid → 回调详情含startTime、activeFrameCallbacksSet当前激活的 id 集合、previousFrameTimestamp、nextCallIdrunCallbacks内部定义loop(timestamp)通过requestAnimationFrame驱动自身循环。每帧计算delta timestamp - previousFrameTimestamp遍历所有激活回调并分发FrameInfo第一帧传timeSincePreviousFrame: null、timeSinceFirstFrame: 0后续帧传真实间隔与累计时间启动条件manageStateFrameCallback在把某回调加入激活集合后调用runCallbacksrunCallbacks判断激活集合大小为 1 且 callId 匹配时才开始requestAnimationFrame(loop)——即第一个回调激活时才启动帧循环停止逻辑当集合为空时递增nextCallId使旧循环失效并把previousFrameTimestamp重置为null保证下次激活时第一帧重新识别为null。此外在原生侧 ReanimatedModuleProxy.h 与 ReanimatedModuleProxy.cpp 中存在pendingFrameCallbacks_等机制负责处理由 worklets 发起的帧回调与上述 UI 线程注册表共同构成完整的帧调度链路。六、行为验证测试用例如何佐证语义仓库中 hooks.useFrameCallback.test.tsx 用testing-library/react-native的renderHook与 Jest 假定时器系统性地验证了本文所述的全部语义返回结构setActive为函数、isActive为布尔、callbackId为数字autostart 语义默认true时激活传false时不激活setActive 切换setActive(true/false)同步更新isActive逐帧调用激活后flushFrames(20)能触发多次回调且首帧timeSincePreviousFrame null、timeSinceFirstFrame 0后续帧各字段均为数字启停行为autostart false时即使推进多帧也不触发setActive(true)后恢复触发setActive(false)后调用次数不再增长生命周期组件卸载unmount后回调停止触发对应 JS 侧清理函数注销注册表的实现。这些测试直接对应 useFrameCallback.ts 与 FrameCallbackRegistryUI.ts 中的实现逻辑可当作行为规格说明来阅读。七、注意事项与使用限制版本要求useFrameCallback自 v2.10.0 起可用使用前请确认 react-native-reanimated 版本满足要求回调必须是 workletcallback 在 UI 线程执行需遵循 worklet 的约束可使用useSharedValue等 UI 线程数据但不可直接访问 JS 线程闭包中的普通变量首帧null判定timeSincePreviousFrame在第一帧为null使用前务必做空值判断如示例中的if (frameInfo.timeSincePreviousFrame null)帧循环是共享的多个激活回调共享同一个requestAnimationFrame循环新回调激活时不会重复开循环停止最后一个回调时循环整体结束随组件卸载自动注销组件卸载后回调自动停止并注销无需手动清理但若在卸载后仍持有并调用setActive需注意callbackId已被重置为-1UI 线程侧的manageStateFrameCallback会对-1直接返回见 FrameCallbackRegistryUI.ts。八、总结useFrameCallback为 react-native-reanimated 提供了低层、灵活的逐帧回调能力autostart控制注册后是否立即运行返回的FrameCallback支持运行时启停FrameInfo的三个字段timestamp、timeSincePreviousFrame、timeSinceFirstFrame覆盖了帧时间戳、帧间隔与累计时间的全部需求。结合 useFrameCallback.ts、FrameCallbackRegistryJS.ts 与 FrameCallbackRegistryUI.ts 的实现可以看到它本质上是JS 侧注册 调度到 UI 线程 requestAnimationFrame驱动帧循环 按需分发 FrameInfo的完整机制。无论驱动逐帧动画、测量帧间隔还是做时间累积它都是值得优先考虑的标准方案。【免费下载链接】react-native-reanimatedReact Natives Animated library reimplemented项目地址: https://gitcode.com/GitHub_Trending/re/react-native-reanimated创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考