
react-native-worklets Babel Plugin 完全指南worklet 化编译原理、自动 worklet 化与边界条件【免费下载链接】react-native-reanimatedReact Natives Animated library reimplemented项目地址: https://gitcode.com/GitHub_Trending/re/react-native-reanimated导读Worklets Babel Plugin 是 react-native-worklets 的核心编译层它把标记了worklet;指令的函数以及处于可 worklet 化上下文中的回调转换成可序列化对象从而让这些函数能够在 Worklet RuntimeUI Runtime 与 Worker Runtime上执行这一过程被称为 workletization。本文以 version-0.9 版本文档 为骨架结合当前仓库 plugin/src 的源码实现完整讲解哪些语法可以被 worklet 化、自动 worklet 化的运作机制、以及它的边界与陷阱。读完本文你将掌握worklet;指令的适用场景、自动 worklet 化的触发规则、Worklet Classes 与 Worklet Context Objects 的用法并能写出在 UI 线程上正确运行的 worklet 代码。什么是 Worklets Babel PluginWorklets 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它注册了针对CallExpression、ClassDeclaration、ClassMethod、Program、JSXAttribute等 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 Declarationsfunction foo() { worklet; console.log(Hello from FunctionDeclaration); }函数表达式Function Expressionsconst foo function () { worklet; console.log(Hello from FunctionExpression); };箭头函数表达式Arrow Function Expressionsconst foo () { worklet; console.log(Hello from ArrowFunctionExpression); };对象方法Object Methodsconst 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 ObjectsWorklet 上下文对象对象方法在 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 ClassesWorklet 类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.tsprocessIfWorkletClass会先移除__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 的已知限制PitfallsWorklet Classes 不支持继承inheritance。Worklet Classes 不支持静态方法和静态属性static methods and properties。类的实例不能在 JS 线程和 UI 线程之间共享。自动 worklet 化Autoworkletization为了减少样板代码并提供更安全的 APIWorklets 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 行useAnimatedStyle、useAnimatedProps、useDerivedValue、useAnimatedReaction、useFrameCallback、useAnimatedScrollHandler、createAnimatedPropAdapter动画回调withTiming/withSpring/withDecay/withRepeat以及调度函数runOnUI/scheduleOnUI/runOnUISync/runOnUIAsync/runOnRuntime/scheduleOnRuntime等reanimatedObjectHooks对象型 hooks第 16-19 行useAnimatedScrollHandler以及全部手势处理对象 hooksreanimatedFunctionArgsToWorkletize参数位置映射第 48-73 行精确指定每个 API 的哪个参数需要被 worklet 化例如useAnimatedReaction的第 0 和第 1 个参数、withTiming的第 2 个参数、runOnRuntime的第 1 个参数。handleWorkletizableCallback第 83-117 行会解析调用表达式取出被调用的函数名命中名单后按上述参数索引对对应实参递归查找可 worklet 化函数或对象并注入指令。需要留意的是在更高级的使用场景中你可能仍然需要手动把函数标记为 worklet。引用 workletsReferencing 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(Im not a worklet); } return { width: 100 }; }这会自动 worklet 化文件中所有顶层的 JavaScript 术语与 Reanimated 术语适合用于包含多个 worklet 的文件。实现位于 file.tsprocessIfWorkletFile检测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指令。自定义 hooksCustom 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 workletsworklet 不会发生提升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 中内置的全局对象列表如global、Object等或开启了strictGlobal选项则不会捕获而是让其在 worklet 运行时自己的全局作用域中解析。此外directives.ts 在处理worklet指令的同时还会注入use no memo指令用于告知运行时该函数不需要记忆化处理。除了上述内部实现插件的可配置选项如bundleMode、disableWorkletClasses、globals、strictGlobal、importForwarding等的完整说明与配置示例可参考当前仓库的 Worklets Babel Plugin Options 文档。其中与本文直接相关的两个典型场景是使用 Custom Serializables 时可能需要通过disableWorkletClasses关闭 Worklet Classes 支持需要精确控制全局变量跨运行时行为时可组合使用globals与strictGlobal选项。总结Worklets Babel Plugin 通过「指令标记 自动 worklet 化」双通道机制把 JavaScript 函数在编译期改造成可序列化、可在 Worklet Runtime 上执行的 worklet显式路径任何函数声明、表达式、箭头函数、对象方法只要在函数体顶部写上worklet;就会被 worklet 化隐式路径useAnimatedStyle、scheduleOnUI等内置 API 的回调、被引用的函数定义、以及聚合 worklet 的对象会被自动 worklet 化文件顶部写worklet;则可整体 worklet 化顶层实体实验性扩展__workletContextObject保留对象方法的this绑定__workletClass让类可以在 UI 线程上实例化不支持继承、静态成员与跨线程共享实例边界清晰跨文件导入的函数、表达式求值产生的函数、以及自定义 hook 的回调不在自动 worklet 化范围内需要手动标记worklet 也不支持提升。把握这些规则你就能在 React Native 开发中准确区分「何时需要手写worklet指令、何时可以交给插件」从而写出稳定运行在 UI 线程上的高性能动画与事件处理代码。如果觉得插件的某些限制过于严格或希望为它贡献新功能可以在本仓库通过 issue 或 discussion 反馈社区也欢迎提交 PR。【免费下载链接】react-native-reanimatedReact Natives Animated library reimplemented项目地址: https://gitcode.com/GitHub_Trending/re/react-native-reanimated创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考