The Main Thread Script is a JS script that can be executed on the main thread. The most common use cases for the main thread script are smooth animations and gesture handling. It is primarily used to address the response delay issue in Lynx's multi-threaded architecture, aiming to achieve a near-native interactive experience.
Here is a simple animation: a small square that moves in sync with a scroll-view
. In the small square component, we listen to the scroll event of the scroll-view
, retrieve the current scroll position from the event parameters, and update its position immediately:
You can try scrolling the scroll-view on the left side of the example. The blue square on the right side of the page will follow the scroll-view's movement. However, you might notice that its movement has an unpredictable delay, especially on devices with lower performance. This delay will also increase as the complexity of the page increases.
This is because in Lynx's architecture, events are triggered on the main thread, while regular JS event handlers can only be executed on background threads. Therefore, if you use regular touch events to trigger animations, the event trigger -> event handling -> rendering process will involve multiple thread switches, resulting in untimely responses and animations lagging behind gestures.
The main thread script provides the capability to handle events synchronously on the main thread, ensuring synchronous event responses.
Synchronizing events using main thread script is very simple. Here we try to modify the previous example.
First, we inform the framework that we want to handle this event on the main thread by adding a main-thread namespace to the event attribute name:
Since the onScroll function is now a main thread event handler, we also need to declare the event handler as a main thread function.
This is done by adding a main thread
directive as the first line inside the function body:
After declaring it as a main thread function, we can no longer call it from the background thread.
Finally, we can now directly manipulate the element's properties on the main thread, so there's no need to use a state to change the position.
When using a main thread function as an event handler, the main thread function accepts an event
parameter that contains basic information about the event.
The event.target
and event.currentTarget
parameters differ from those in regular event handlers;
they are MainThread.Element
objects.
This object allows you to conveniently synchronize the retrieval and setting of node properties, such as using setStyleProperty()
in the example.
That's all the changes needed. We will place the components before and after the modification in the same example for you to compare the effects. You may notice that the animation delay has disappeared!
You may have noticed that designating a function as a main thread function isolates it from its surrounding context, making it feel like an "island." Its runtime environment is different from other functions, meaning it cannot freely communicate with the background thread or other main-thread scripts. However, we sometimes still need data from the background thread.
Fortunately, obtaining data from the background thread inside a main thread function is straightforward: just use it directly, as if it were a normal function.
When the main thread function is defined, it automatically captures external variables from the background thread, such as the red variable in the example above. However, you cannot directly modify the values in the background thread.
The values captured by the main thread function are not updated in real time.
Instead, they are synchronized from the background thread to the main thread only after the component containing the main thread function re-renders.
Additionally, the synchronization requires that the captured values be serializable using JSON.stringify()
.
To summarize the precautions:
JSON.stringify()
, so they must be serializable to JSON.main-thread:ref
to Obtain Node ObjectsIn the example above, clicking on the text would change the color of both lines of text. If we want to change the color of only the first line of text when clicking on the text, it is not easy to achieve this with just event.target
and event.currentTarget
. In this case, you can use main-thread:ref
to obtain a node object usable on the main thread (MainThread.Element
).
Create a MainThreadRef
using the useMainThreadRef()
Hook, and then assign it to the target node's main-thread:ref
attribute:
Note that the current
property of MainThreadRef
can only be accessed within a main thread function.
main-thread:ref
Similar to a regular ref
, you can also pass a main thread function to main-thread:ref
:
You can also return a cleanup function in the main thread function passed to main-thread:ref
, just like when using a regular ref
.
main-thread:ref
in Class ComponentsIf you are using traditional class components, you cannot use the useMainThreadRef()
Hook. Instead, you can directly create a MainThreadRef
object:
Main thread functions cannot modify captured variables. Therefore, if you need to maintain state between main thread functions, you should use MainThreadRef
.
For example, changing the background color of a node based on the number of clicks:
Use runOnMainThread()
in the background thread to asynchronously execute a main thread function on the main thread:
Use runOnBackground()
on the main thread to asynchronously execute a regular function on the background thread: