主线程脚本(Main Thread Script)是能够在主线程执行的 JS 脚本。主线程脚本的最常使用场景是跟手动画和手势处理,主要用于解决 Lynx 多线程架构下的响应延迟问题,来达到近似原生的交互体验。
这里有一个简单的动画:一个与 scroll-view 同步移动的小方块。在小方块组件中,我们监听了滚动视图的滚动事件,从事件参数中获取当前滚动位置,并立即更新自己的位置:

你可以尝试滚动示例左侧的 scroll-view。页面右侧的蓝色方块将会随之移动。然而,你可能会注意到它的移动存在一种难以预料的延迟,尤其是在低性能设备上。当页面的复杂度增加时,这种延迟也会相应加剧。
这是因为在 Lynx 的架构下,事件会在主线程触发,而普通的事件处理函数只能在后台线程执行。因此事件触发 -> 事件处理 -> 绘制流程中会有多次线程切换,导致响应不及时,动画效果相比手势滞后。

为了解决这个问题,Lynx 提供了主线程脚本。主线程脚本通过在主线程同步处理事件的能力保证事件能够被同步响应。

使用主线程脚本同步处理事件非常简单。这里我们尝试修改一下之前的示例。
首先,我们通过在事件属性名称中添加 main-thread 命名空间来通知框架我们想要在主线程上处理此事件:
我们还需要将 onScroll 函数声明为主线程函数。实现方式是:在函数体的首行添加一个 'main thread' 指令。
在将其声明为主线程函数后,我们就不能再从后台线程调用它了。
最后,因为我们现在可以在主线程中直接操作元件的属性,所以不再需要通过 state 来改变位置。
在将 主线程函数用作事件处理器时,主线程函数接受一个 event 参数,包含事件的基本信息。
其 event.target 和 event.currentTarget 参数和普通的事件处理器不同,是一个 MainThread.Element 对象。
通过这个对象可以方便地同步获取和设置节点的属性,例如示例中的 setStyleProperty()。
以上就是所需的全部更改了。我们会在同一个示例中放置修改前后的组件,方便你对比效果。你可能会注意到,动画的延迟已经消失了!

你可能已经发现,将一个函数指定为主线程函数会使其从周围的上下文环境中脱离出来,就像一个“孤岛”。 它和其他函数的运行环境不同,因此无法自由地和后台线程或其他主线程脚本通信。 但我们有时还是需要后台线程的一些数据。
幸运的是,在主线程函数中获取后台线程的数据非常简单:只需要直接使用它,就像在一个普通函数中一样:
主线程函数在定义时会自动捕获后台线程的外部变量,如上面示例中的 red。不过,你不能直接改变后台线程的值。
主线程函数所捕获的值并不是实时更新的。每次主线程函数所在的组件重新渲染后,这个值才会从后台线程同步到主线 程。这个同步过程还要求所捕获的值必须是可通过 JSON.stringify() 序列化的。
总结一下注意事项:
JSON.stringify() 在线程间传递,因此必须可序列化为 JSON。main-thread:ref 获取节点对象在上面的示例中,对文字进行点击会使得两行文字同时变色。如果我们希望在点击文字时只让第一行文字变色,只靠 event.target 和 event.currentTarget 就不容易完成。
此时,可以通过 main-thread:ref 来获取主线程可用的节点对象(MainThread.Element)。
通过 useMainThreadRef() Hook 创建一个 MainThreadRef,然后将其赋值给目标节点的 main-thread:ref 属性:
注意 MainThreadRef 的 current 属性只能在主线程函数中访问。