react-native-worklets Babel Plugin 完全指南:worklet 化编译原理、自动 worklet 化与边界条件
2026/9/15 16:06:57 网站建设 项目流程

react-native-worklets Babel Plugin 完全指南:worklet 化编译原理、自动 worklet 化与边界条件

【免费下载链接】react-native-reanimatedReact Native's Animated library reimplemented项目地址: https://gitcode.com/GitHub_Trending/re/react-native-reanimated

导读

Worklets Babel Plugin 是 react-native-worklets 的核心编译层:它把标记了'worklet';指令的函数(以及处于可 worklet 化上下文中的回调)转换成可序列化对象,从而让这些函数能够在 Worklet Runtime(UI Runtime 与 Worker Runtime)上执行,这一过程被称为 workletization。本文以 version-0.9 版本文档 为骨架,结合当前仓库 plugin/src 的源码实现,完整讲解哪些语法可以被 worklet 化、自动 worklet 化的运作机制、以及它的边界与陷阱。读完本文,你将掌握'worklet';指令的适用场景、自动 worklet 化的触发规则、Worklet Classes 与 Worklet Context Objects 的用法,并能写出在 UI 线程上正确运行的 worklet 代码。

什么是 Worklets Babel Plugin

Worklets Babel Plugin 是一个 Babel 编译插件,它的职责是在打包阶段改写你的代码,使其可以在 Worklet Runtime 上执行。它会寻找以下两类函数,并把它们转换成可序列化对象:

  • 函数体最顶部包含'worklet'指令的函数,例如:
function foo() { 'worklet'; console.log('Hello from worklet'); }
  • 处于自动 worklet 化(autoworkletizable)上下文中的函数,例如:
useAnimatedStyle(() => { // 该函数会在 UI 线程上运行, // 因为它处于可 worklet 化的上下文中,会被自动 worklet 化。 // 这里无需手动添加 'worklet' 指令。 return { width: 100, }; });

从源码看,插件入口定义在 plugin.ts,它注册了针对CallExpressionClassDeclarationClassMethodProgramJSXAttribute等 AST 节点的访问器(visitor)。其中值得注意的实现细节是:插件在pre阶段就运行一个「自动 worklet 化微插件」(getAutoworkletizationMicroPlugin),提前为可自动 worklet 化的回调添加'worklet'指令——源码注释说明这是为了赶在 React Compiler 处理Program节点之前完成指令注入。最终,被标记的函数会通过 workletSubstitution.ts 中的processWorklet被替换为 worklet 工厂调用(worklet factory call)。

什么可以被 worklet 化

JavaScript 术语

Worklets Babel Plugin 支持将以下四种 JavaScript 语法形式作为 worklet 处理:

函数声明(Function Declarations)
function foo() { 'worklet'; console.log('Hello from FunctionDeclaration'); }
函数表达式(Function Expressions)
const foo = function () { 'worklet'; console.log('Hello from FunctionExpression'); };
箭头函数表达式(Arrow Function Expressions)
const foo = () => { 'worklet'; console.log('Hello from ArrowFunctionExpression'); };
对象方法(Object Methods)
const obj = { foo() { 'worklet'; console.log('Hello from ObjectMethod'); }, };

源码层面,这四种形式统一由WorkletizableFunction类型描述(见 types.ts),在 findWorklet.ts 中通过isWorkletizableFunctionPath判定函数路径是否为可 worklet 化函数。一个值得注意的细节是:对于箭头函数,若其函数体不是块语句(例如() => 1这种隐式返回形式),directives.ts 中的replaceImplicitReturnWithBlock会先把它改写成() => { return 1 }——因为指令(directive)只能存在于块语句函数体上。

Reanimated 术语

[实验性] Worklet Context Objects(Worklet 上下文对象)

对象方法在 UI 线程上被调用时,会丢失其this绑定:

const obj = { foo: 1, bar() { 'worklet'; console.log(this.foo); // undefined - 绑定已经丢失。 }, };

Worklet Context Objects是一种特殊术语,用于保留这种绑定。注意不要把它和useSharedValue创建的对象混淆:对 Worklet Context Objects 在 UI 线程上的所有修改,只会在 UI 线程上可见;JS 线程同理,两条线程之间不会互相看到对方的改动。

const obj = { __workletContextObject: true, foo: 1, bar() { console.log(this.foo); }, }; obj.foo = 2; obj.bar(); // 输出 2 scheduleOnUI(() => obj.bar()); // 输出 1 scheduleOnUI(() => (obj.foo = 3)); obj.bar(); // 输出 2 scheduleOnUI(() => obj.bar()); // 输出 3

__workletContextObject是一个特殊的标记属性,它把对象标记为 Worklet Context Object。该属性的值无关紧要,但实践上建议使用true。如果对象带有该属性,其方法中的'worklet'指令会被忽略:

const workletContextObject = { __workletContextObject: true, message: 'Hello from WorkletContextObject', foo() { console.log(this.message); }, };
[实验性] Worklet Classes(Worklet 类)

React Native 使用的 JavaScript 引擎 Hermes 本身不支持 class 语法,class 语法需要经过 polyfill(垫片)处理后才能使用,而这在 UI 线程上是有问题的。为了绕开这一点,react-native-worklets 提出了Worklet Classes的概念——worklet 类可以直接在 UI 线程上实例化。

__workletClass是一个特殊属性,用于把某个类标记为 Worklet Class。属性值无关紧要,但建议使用true。如果类带有该属性,其方法中的'worklet'指令会被忽略:

class Clazz { __workletClass = true; message = 'Hello from WorkletClass'; foo() { console.log(this.message); } } scheduleOnUI(() => new Clazz().foo()); // 输出 'Hello from WorkletClass'

从源码看,Worklet Classes 的实现位于 class.ts:processIfWorkletClass会先移除__workletClass标记,然后调用 getPolyfilledAst 借助@babel/plugin-transform-class-properties@babel/plugin-transform-classes等插件生成 polyfill 后的 AST,再对 polyfill 出的函数逐个追加'worklet'指令(appendWorkletDirectiveToPolyfills),最后把类声明替换为「工厂函数 + 调用」的形式(replaceClassDeclarationWithFactoryAndCall)。由于多个 polyfill 函数之间存在相互依赖,源码还实现了一套基于拓扑排序(topoSort)的 polyfill 排序逻辑。此外,plugin.ts 的ClassDeclarationvisitor 会在disableWorkletClasses选项开启时跳过该处理。

Worklet Classes 的已知限制(Pitfalls):

  • Worklet Classes 不支持继承(inheritance)。
  • Worklet Classes 不支持静态方法和静态属性(static methods and properties)。
  • 类的实例不能在 JS 线程和 UI 线程之间共享。

自动 worklet 化(Autoworkletization)

为了减少样板代码并提供更安全的 API,Worklets Babel Plugin 会自动检测一个函数是否应该被 worklet 化。得益于这一点,你不需要为回调手动添加'worklet'指令:

import { scheduleOnUI } from 'react-native-worklets'; const style = scheduleOnUI((greetings: string) => { // 这里不需要添加 'worklet' 指令, // 因为插件会检测到这个回调是自动 worklet 化的。 console.log(`${greetings} from UI Runtime`); }, 'Hello');

这种能力并不局限于useAnimatedStyle——Worklets Babel Plugin 会为其所有 API 的回调做自动 worklet 化。此外,它也会对 React Native Reanimated 的布局动画回调和 React Native Gesture Handler 的部分回调做同样的处理。

自动 worklet 化的完整名单维护在 autoworkletization.ts 中,主要包括三张表:

  • reanimatedFunctionHooks(函数型 hooks,第 21-46 行):useAnimatedStyleuseAnimatedPropsuseDerivedValueuseAnimatedReactionuseFrameCallbackuseAnimatedScrollHandlercreateAnimatedPropAdapter,动画回调withTiming/withSpring/withDecay/withRepeat,以及调度函数runOnUI/scheduleOnUI/runOnUISync/runOnUIAsync/runOnRuntime/scheduleOnRuntime等;
  • reanimatedObjectHooks(对象型 hooks,第 16-19 行):useAnimatedScrollHandler以及全部手势处理对象 hooks;
  • reanimatedFunctionArgsToWorkletize(参数位置映射,第 48-73 行):精确指定每个 API 的哪个参数需要被 worklet 化,例如useAnimatedReaction的第 0 和第 1 个参数、withTiming的第 2 个参数、runOnRuntime的第 1 个参数。

handleWorkletizableCallback(第 83-117 行)会解析调用表达式,取出被调用的函数名,命中名单后按上述参数索引对对应实参递归查找可 worklet 化函数或对象,并注入指令。

需要留意的是:在更高级的使用场景中,你可能仍然需要手动把函数标记为 worklet。

引用 worklets(Referencing worklets)

你可以在函数定义之前就引用它,插件同样会把它自动 worklet 化:

function foo() { // 这里不需要添加 'worklet' 指令。 return { width: 100 }; } // 这里不需要定义内联函数,直接传引用即可。 const style = useAnimatedStyle(foo);

底层原理位于 referencedWorklets.ts:当实参是一个标识符(Identifier)时,findReferencedWorklet会通过 Babel 的 scope 绑定系统(scope.getBinding)回溯该标识符对应的函数声明或变量声明;若绑定是常量(binding.constant),则从变量声明初始化器(VariableDeclarator.init)中继续查找,否则从赋值表达式(AssignmentExpression)中查找。也就是说,无论是function foo声明、const foo = () => {...}赋值、还是先声明后引用的引用链,插件都能追踪到真正的函数定义并注入 worklet 指令。

聚合 worklets 的对象(Objects aggregating worklets)

在某些 API 中(例如useAnimatedScrollHandler),你可以传入一个包含多个 worklet 方法的对象,而不是单个函数:

const handlerObject = { // 这些方法无需标记为 worklet。 onBeginDrag() { console.log('Dragging...'); }, onScroll() { console.log('Scrolling...'); }, }; const handler = useAnimatedScrollHandler(handlerObject);

对应实现见 findWorklet.ts 的forEachWorkletizableObjectProperty:插件会遍历对象的每个属性,ObjectMethod直接回调注入指令,ObjectProperty则对其值递归执行forEachWorkletizableFunction;如果属性类型不在支持范围内,会抛出异常提示「该属性类型不支持用于对象 hooks」。

[实验性] worklet 化整个文件(Workletizing whole files)

你可以在文件顶部添加'worklet'指令,把整个文件标记为可 worklet 化文件:

// file.ts 'worklet'; function foo() { // 函数 'foo' 会被自动 worklet 化。 return { width: 100 }; } function bar() { // 函数 'bar' 会被自动 worklet 化。 function foobar() { // 函数 'foobar' 不会,因为它不在顶层作用域中定义。 console.log("I'm not a worklet"); } return { width: 100 }; }

这会自动 worklet 化文件中所有顶层的 JavaScript 术语与 Reanimated 术语,适合用于包含多个 worklet 的文件。

实现位于 file.ts:processIfWorkletFile检测Program节点的指令中是否存在'worklet',找到后先移除该指令,再调用processWorkletFile遍历顶层语句。对于每个顶层实体:函数声明 / 函数表达式直接注入指令;对象字面量递归处理其方法;VariableDeclaration逐个处理初始化器;ClassDeclaration则追加__workletClass标记并登记到待 worklet 化列表(state.classesToWorkletize)。这里还有一个实用细节:dehoistCommonJSExports(第 129-146 行)会把 CommonJS 导出语句(exports.xxx = ...module.exports = ...)挪到文件末尾,以避免它们干扰 worklet 化处理。

自动 worklet 化的限制(Limits of autoworkletization)

在某些上下文中,插件无法推断一个函数是否应当被自动 worklet 化。

导入(Imports)

当你从另一个文件或模块导入函数并将其用作 worklet 时,必须手动为该函数添加'worklet'指令:

// foo.ts import { bar } from './bar'; // ... const style = useAnimatedStyle(bar); // bar.ts export function bar() { 'worklet'; // 不加这个指令就不生效。 return { width: 100, }; }

结合上一节的引用解析逻辑可以理解这一限制:useAnimatedStyle(bar)中的bar是导入绑定,referencedWorklets.ts只能回溯到import声明而非函数体,无法为另一个文件中的函数体注入指令,因此跨文件导入的函数必须自带'worklet'指令。

自定义 hooks(Custom hooks)

目前 Reanimated 还没有暴露可以让你注册自定义 hook 以对其回调做 worklet 化的 API。不过,这一能力未来可能会加入。

表达式(Expressions)

当一个函数是表达式(expression)的求值结果时,它不会被自动 worklet 化,必须手动添加'worklet';指令:

const foo = someCondition ? () => { 'worklet'; // 不加这个指令就不生效。 return { width: 100 }; } : () => { 'worklet'; // 不加这个指令就不生效。 return { width: 200 }; }; const style = useAnimatedStyle(foo);

对于这种情况,官方建议要么把条件逻辑放进 worklet 内部处理,要么重构代码以避免条件式 worklet。

陷阱(Pitfalls)

有些写法在插件下无法工作。

worklet 的提升(Hoisting worklets)

worklet 不会发生提升(hoisting),这意味着你不能在使用 worklet 之前引用它:

// 下面这一行会崩溃, // 尽管 'foo' 已经被标记为 worklet。 const style = useAnimatedStyle(foo); function foo() { 'worklet'; return { width: 100 }; }

深入:worklet 编译产物与闭包处理

为了把 worklet 变成可序列化对象,workletFactory.ts 中的makeWorkletFactory会把每个 worklet 编译成一个工厂函数(factory)。这一过程会剥离源码中的'worklet'指令(stripWorkletDirectives,第 444-457 行),通过@babel/generator生成函数源码字符串,并附加若干内部标记:

  • __workletHash:由函数源码字符串哈希得到的数字(hash函数见第 465-480 行),用于在运行时唯一标识该 worklet;
  • __closure:闭包变量对象——worklet 捕获的外部变量会被放进这个对象并随 worklet 一起拷贝到目标运行时;
  • __initData:包含code(函数源码字符串)、location(源文件位置,供调试堆栈使用)、sourceMap(源码映射)等初始化数据;启用 hermesBytecode 选项 时,这里存放的是预编译的 Hermes 字节码;
  • __stackDetails(非 release 构建):包含Error实例与行偏移量,用于还原准确的调用栈(调试期才有);
  • __pluginVersion(非 release 构建):插件版本号,用于运行时的版本一致性校验。

闭包变量的捕获逻辑在 closure.ts 中实现:插件遍历 worklet 函数体中的所有引用标识符,通过 scope 绑定判断它们是否来自外部作用域;对于无绑定(unbound)的标识符,如果命中了默认的全局黑名单(见 globals.ts 中内置的全局对象列表,如globalObject等)或开启了strictGlobal选项,则不会捕获,而是让其在 worklet 运行时自己的全局作用域中解析。此外,directives.ts 在处理'worklet'指令的同时还会注入'use no memo'指令,用于告知运行时该函数不需要记忆化处理。

除了上述内部实现,插件的可配置选项(如bundleModedisableWorkletClassesglobalsstrictGlobalimportForwarding等)的完整说明与配置示例,可参考当前仓库的 Worklets Babel Plugin Options 文档。其中与本文直接相关的两个典型场景是:使用 Custom Serializables 时可能需要通过disableWorkletClasses关闭 Worklet Classes 支持;需要精确控制全局变量跨运行时行为时,可组合使用globalsstrictGlobal选项。

总结

Worklets Babel Plugin 通过「指令标记 + 自动 worklet 化」双通道机制,把 JavaScript 函数在编译期改造成可序列化、可在 Worklet Runtime 上执行的 worklet:

  • 显式路径:任何函数(声明、表达式、箭头函数、对象方法)只要在函数体顶部写上'worklet';就会被 worklet 化;
  • 隐式路径useAnimatedStylescheduleOnUI等内置 API 的回调、被引用的函数定义、以及聚合 worklet 的对象会被自动 worklet 化;文件顶部写'worklet';则可整体 worklet 化顶层实体;
  • 实验性扩展__workletContextObject保留对象方法的this绑定,__workletClass让类可以在 UI 线程上实例化(不支持继承、静态成员与跨线程共享实例);
  • 边界清晰:跨文件导入的函数、表达式求值产生的函数、以及自定义 hook 的回调不在自动 worklet 化范围内,需要手动标记;worklet 也不支持提升。

把握这些规则,你就能在 React Native 开发中准确区分「何时需要手写'worklet'指令、何时可以交给插件」,从而写出稳定运行在 UI 线程上的高性能动画与事件处理代码。如果觉得插件的某些限制过于严格,或希望为它贡献新功能,可以在本仓库通过 issue 或 discussion 反馈,社区也欢迎提交 PR。

【免费下载链接】react-native-reanimatedReact Native's Animated library reimplemented项目地址: https://gitcode.com/GitHub_Trending/re/react-native-reanimated

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询