Thinking in ReactLynx
ReactLynx follows React's programming model, but leverages the dual-threaded runtimes provided by Lynx to achieve better performance and user experience through its own idioms (or rules).
Your code runs on two threads
When the component <HelloComponent /> is rendered, you will see "Hello" printed twice in the Console.
This happens because the code runs on two threads: main thread and background thread, which are part of Lynx's dual-threaded runtime.
- The main thread is responsible for rendering the initial screen and applying subsequent UI updates. This allows users to see the first screen as quickly as possible while reducing the main thread's workload.
- The background thread runs with a complete React runtime, handling component lifecycles and other side effects. Since complete consistency with single-threaded React is not possible, we have a modified component lifecycle as shown in Component Lifecycle.
Not all code can run on both threads
However, not all code can be executed on both threads.
Consider the following example that adds a listener to GlobalEventEmitter:
When the component <EventListenerComponent /> is rendered, you will see a "not a function" error. This occurs because while Lynx renders this component on both threads, lynx.getJSModule('GlobalEventEmitter') cannot be executed on the main thread.
❓ Why does this error occur?
The reason relates to Lynx's dual-threaded runtime architecture, where the code for <EventListenerComponent /> executes on both threads:
-
On the background thread:
The
lynx.getJSModulefunction is available as part of the LynxGlobalEventEmitterAPI. Therefore, executinglynx.getJSModule('GlobalEventEmitter')works without issues. -
On the main thread:
The
getJSModulefunction does not exist on the main thread. Thus, when this code executes on the main thread,lynx.getJSModuleis evaluated asundefined, leading to the "not a function" error.
Some code can only run on the background thread
Typically, side effects unrelated to rendering cannot be executed on the main thread, such as data updates, event listeners, timers, and network requests. Executing these side effects on the main thread will result in runtime errors. We call code that only executes on the background thread background only code. By marking background only code, we help the compiler optimize code and prevent these side effects from executing on the main thread.
There are three key rules for background only code:
- Rule 1: Code that meets any of these conditions is considered background only.
- Rule 2: Background only code can only be used within other background only code.
- Rule 3: Code that is only used by background only code is considered background only.
Rule 1: Code that meets any of these conditions is considered background only
Lynx considers code that meets any of these conditions as background only:
- Event handlers (e.g.
bindtap/catchtap) - Effects (e.g.
useEffect/useLayoutEffect) refprop anduseImperativeHandle- Functions with
'background only'directive - Modules with
import 'background-only'directive
For example, all these functions with console.log in the following example are considered background only. These functions will neither be bundled into main thread code nor executed on the main thread.
Following this rule, we can see that the earlier <EventListenerComponent /> should move its GlobalEventEmitter usage into useEffect.
This ensures this code only runs on the background thread where the GlobalEventEmitter API is available:
Rule 2: Background only code can only be used within other background only code
When dependencies are involved, things become more complicated. Simply put, background only code can only be called by other background only code.
Rule 3: Code that is only used by background only code is considered background only
Usually, event callbacks of elements are considered background only. handleTap doesn't need the 'background only' directive but will be considered background only code because it's only used as a handler in the bindtap event.
The backgroundOnly function will also be considered background only because it's only called in the useEffect callback.
Exceptions
Due to limitations in compiler analysis capabilities and compile-time performance, there are some exceptions to this rule. We will work to address these issues in future versions.
When event handlers are passed as props, you must add the 'background only' directive. Otherwise, the compiler cannot identify handleTap as background only code and will include it in the main thread bundle:
When using custom Hooks, you must add the 'background only' directive. Otherwise, backgroundOnly will be treated as non-background only code and included in the main thread bundle.
This occurs because the compiler cannot determine if the callback of useMount is only used in background only code.
Some code can only run on the main thread
Just as some code (like GlobalEventEmitter) only works on the background thread, there is also code that can only be executed on the main thread.
Main Thread Script
Main Thread Script (MTS) is script executed on the main thread.
For more details about MTS, including usage examples and best practices for handling animations and gestures on the main thread, please refer to Main Thread Script.
Element PAPIs
Lynx Engine also provides low-level APIs called Element PAPI.
Usually, Element PAPI calls are compiled by ReactLynx and you should not need to write any Element PAPI code.