The React Native architecture relies on React 18, which has been released in beta. The Documentation for the React Native architecture for the ecosystem and core developers has also been released. Kevin Gozali, a member of the React Native team, also spoke in a recent interview about the last stage of delayed initialization of the new architecture, which is expected to be completed in the first half of 2022. There are signs that the React Native architecture is coming.

Earlier, RN announced that Hermes will be the default JS engine for React Native. In this article, we briefly introduced the upcoming new renderer Fabric, so let’s focus on the new renderer Fabric.

A, the Fabric

1.1 Basic Concepts

Fabric is the render system of React Native’s new architecture, which evolved from the render system of the older architecture. The core principle is to unify more rendering logic on the C++ layer, improve interoperability with host platforms, i.e. be able to call JavaScript code synchronously on UI threads, and significantly improve rendering efficiency. Fabric development began in 2018, and many React Native apps within Facebook use the new renderer Fabric.

Before we introduce the new Renderer, let’s introduce a few words and concepts:

  • Host Platform: The platforms on which React Native is embedded, such as Android, iOS, Windows, and macOS.
  • Fabric Renderer: The Fabric Renderer implemented by React Native is the same code that executes on the Web as React Native. However, React Native renders the common platform view (host view) rather than the DOM node (think of DOM as the host view of the Web).

After changing the underlying rendering process, the Fabric renderer makes rendering the host view feasible. Fabric lets React communicate directly with individual platforms and manage their host view instances. The Fabric renderer exists in JavaScript, and it calls the interface exposed by C++ code.

1.2 Original intention of the new renderer

The original intention of the new rendering architecture was to improve the user experience, which was not possible on the old architecture. Mainly embodied in:

  • To improve interoperability between host views and React views, renderers must be able to measure and render the React interface simultaneously. In older architectures, React Native layouts were asynchronous, which resulted in layout “jitter” issues when rendering nested React Native views in the host view.
  • With multiple priorities and the ability to synchronize events, renderers can prioritize user interactions to ensure that their actions are processed in a timely manner.
  • React Suspense integration allows developers to organize requests for data code in React more efficiently.
  • Allows developers to interrupt rendering in React Native using React Concurrent.
  • React Native server rendering is easier to implement.

In addition, the new Fabric renderer is a qualitative improvement in code quality, performance, and extensibility.

  • Type safety: Code Generation ensures type safety for both JS and host platforms. The code generation tool uses JavaScript component declarations as the sole source of facts to generate C++ constructs to hold the props property. Build errors do not occur because the JavaScript and host component props properties do not match.
  • Shared C++ core: renderers are implemented in C++ and their core is shared between platforms. This increases consistency and makes it easier for new platforms to adopt React Native. (VR new platform, for example)
  • Better host platform interoperability: Synchronous and thread-safe layout computing improves the user experience when host components are integrated into React Native.
  • Performance improvements: The implementation of the new rendering system is cross-platform, with each platform getting a better user experience from performance improvements that were originally implemented only on a particular platform. For example, the Flatview hierarchy, which used to be a performance optimization on Android, is now available on Both Android and iOS.
  • Consistency: The implementation of the new rendering system is cross-platform, making it easier to be consistent across platforms.
  • Faster startup: By default, the initialization of the host component is performed lazily.
  • Less data serialization between JS and the host platform: React uses serialized JSON to pass data between JavaScript and the host platform. The new renderer uses JSI (JavaScript Interface) to fetch JavaScript data directly.

Second, the rendering process

2.1 Rendering Process

The React Native renderer renders React code to the host platform through a series of processes. This series of processes is called a render pipeline, which initializes rendering and UI state updates. Next, we’ll focus on the React Native rendering pipeline and how it differs in various scenarios.

The rendering pipeline can be roughly divided into three stages:

  • Render: In JavaScript, React executes the production logic code to create React Element Trees. Then create a React Shadow Tree using the React element Tree in C++.
  • Commit: After the React shadow tree is fully created, the renderer triggers a commit. This elevates the React element tree and the newly created React shadow tree to “next tree to mount”. This process also includes calculation of layout information.
  • Mount: The React shadow Tree is converted to a Host View Tree once it has the result of the layout calculation.

Here are a few nouns that need to be explained:

The React element tree

The React element tree is created using JavaScript React. The tree consists of a family of React class elements. A React element is a plain JavaScript object that describes what needs to be displayed on the screen. An element includes attributes props, styles, and children. The React element is divided into two classes: React Composite Components and React Host Components instances, and it only exists in JavaScript.

The React shadow tree

The React shadow tree is created using the Fabric renderer and consists of a series of React shadow nodes. A React shadow node is an object representing a mounted React host component that contains properties derived from JavaScript. It also contains layout information, such as coordinates x, y, width, height. In the new Fabric renderer, the React shadow node object exists only in C++. In older architectures, it existed in the stack of the phone’s runtime, such as Android’s JVM.

Host view tree

A host view tree is a series of host views on Android, iOS, and so on. On Android, the host view is an instance of Android.view. ViewGroup, an instance of Android.Widget.textView, and so on. Host views form the host view tree like building blocks. The size and coordinate position of each host view is based on LayoutMetrics, which are calculated using Yoga, the React Native layout engine. The style and content information for the host view is obtained from the React shadow tree.

The phases of the React Native rendering pipeline may occur in different threads, as described in the Thread model section.In React Native, there are usually three types of operations that involve rendering:

  • Initialize render
  • React Status Update
  • React Native renderer status updates

2.2 Initialize render

2.2.1 Rendering stage

Suppose you have the following component that needs to render:

function MyComponent() {
  return (
    <View>
      <Text>Hello, World</Text>
    </View>
  );
}
Copy the code

In the example above,<MyComponent />React will eventually be reduced to the most basic React host element. Call the function component MyComponet or the render method of the class component recursively each time until all components have been called. The result is a React element tree for the React host component.Here, there are a few important terms that need to be explained.”

  • React component: The React component is a JavaScript function or class that describes how to create React elements.
  • The React composite component: The Render method of the React component includes other React composite components and the React host component. (Composite components are declared by developers)
  • React host componentThe React component views through the host view, for example<View>,<Text>The implementation. On the Web, the host component of the ReactDOM is<p>Label,<div>The component that the tag represents.

During element simplification, every time a React element is called, the renderer creates React shadow nodes simultaneously. This process only happens on the React host component, not the React composite component. For example, a

creates a ViewShadowNode object, and a

creates a TextShadowNode object.

does not have a React shadow node because it is not a base component.


While React creates a parent-child relationship for the two React element nodes, the renderer also creates the same parent-child relationship for the corresponding React shadow nodes. The code above, the product of each render stage, is shown below.

2.2.2 Submission phase

After the React shadow tree is created, the renderer triggers a React element tree submission.The Commit Phase consists of two operations: layout calculation and tree promotion.

Layout calculation

This step calculates the position and size of each React shadow node. In React Native, the layout of each React shadow node is calculated using the Yoga layout engine. The actual calculation takes into account the style of each React shadow node, which comes from the React element in JavaScript. The calculation also takes into account the layout constraints of the root node of the React shadow tree, which determines how much free space the final node can have.

Tree ascension

Tree Promotion (New Tree → Next Tree) this step promotes the New React shadow Tree to the Next Tree to be mounted. This upgrade means that the new tree has all the information to mount and represents the latest state of the React element tree. The next tree will be mounted on the next “tick” in the UI thread.

Also, most layout calculations are performed in C++, with only some components, such as Text and TextInput components, performing layout calculations on the host platform. The size and position of the text is unique to each host platform and needs to be computed at the host platform layer. To do this, the Yoga layout engine calls functions of the host platform to calculate the layout of these components.

2.2.3 Mount Phase

The Mount Phase transforms the React shadow tree, which already contains layout calculations, into a host view tree rendered in pixels on the screen.

At a higher level of abstraction, the React Native renderer creates host views for each React shadow node and mounts them on the screen. In the example above, the renderer creates an instance of Android.view. ViewGroup for

and an instance of Android.widget.textView for

with Text content of “Hello World”. IOS is similar, creating a UIView and calling NSLayoutManager to create text. The host view is then configured with properties from the React shadow node. The size and position of these host views are configured using the calculated layout information.

The mount phase is further broken down into three steps:

  • Tree comparison: this step is calculated entirely in C++ and compares the element differences between the “rendered tree” and the “next tree”. The result is a series of atomic change operations on the host platform, such as createView, updateView, removeView, deleteView, and so on. In this step, the React shadow tree is refactored to avoid unnecessary host view creation.

  • Tree lift, from next tree to rendered tree: In this step, “Next Tree” is automatically promoted to “previously rendered tree” so that in the next mount phase, the correct tree is used for comparison calculations.

  • View mount: This step performs atomic changes on the corresponding native view, which occurs on the UI thread of the native platform.

Meanwhile, all operations during the mount phase are performed synchronously in the UI thread. If the commit phase is executed in the background thread, then the mount phase is executed in the next “tick” of the UI thread. Also, if the commit phase is performed in the UI thread, the mount phase is also performed in the UI thread. Scheduling and execution of the mount phase depends heavily on the host platform. For example, the current rendering architecture of the Android and iOS mount layers is different.

2.3 React Status Update

Next, we’ll look at the various stages of the render pipeline during React state updates. Suppose the following components are rendered when the render is initialized.

function MyComponent() {
  return (
    <View>
      <View
        style={{ backgroundColor: 'red', height: 20, width: 20 }}
      />
      <View
        style={{ backgroundColor: 'blue', height: 20, width: 20 }}
      />
    </View>
  );
}
Copy the code

By initializing the rendering part of the knowledge, we can get the following three trees:As you can see, node 3 has a red host view background and node 4 has a blue host view background. Suppose the JavaScript production logic is to change the first embedded background color from red to yellow. The new React element tree looks something like this.

<View>
  <View
    style={{ backgroundColor: 'yellow', height: 20, width: 20 }}
  />
  <View
    style={{ backgroundColor: 'blue', height: 20, width: 20 }}
  />
</View>
Copy the code

At this point, we might be wondering: How does React Native handle this update?

Conceptually, when a state update occurs, the renderer needs to update the React element tree directly in order to update the mounted host view. However, to be thread-safe, both the React element tree and the React shadow tree must be immutable. This means that React does not directly change the current React element tree and React shadow tree, but instead must create a new copy of each tree with new properties, new styles, and new child nodes.

2.3.1 Rendering stage

React creates a new React element tree with the new state, which copies all the changed React elements and React shadow nodes. After copying, submit a new React element tree.

The React Native renderer minimizes the overhead of immutable features by taking advantage of structure sharing. To update the React element’s new state, all elements along the path from that element to the root element need to be copied. React, however, only copies React elements with new attributes, new styles, or new child elements. Any React elements that have not changed due to state updates are not copied. Instead, React elements are shared between the new and old trees.

In the example above, React creates a new tree using the following operations:

  1. CloneNode(Node 3, {backgroundColor: ‘yellow’}) → Node 3′
  2. CloneNode(Node 2) → Node 2′
  3. AppendChild(Node 2′, Node 3′)
  4. AppendChild(Node 2′, Node 4)
  5. CloneNode(Node 1) → Node 1′
  6. AppendChild(Node 1′, Node 2′)

When this is done, Node 1′ is the root of the new React element tree. We use T for “previously rendered tree” and T’ for “new tree”.Note that node 4 is shared between T and T’. Structural sharing improves performance and reduces memory usage.

2.3.2 Submission phase

After React creates the React element tree and the React shadow tree, we need to commit them, which also involves the following steps:

  • Layout calculation: Layout calculation for status updates, similar to layout calculation for initial rendering. One important difference is that layout calculations may cause the shared React shadow nodes to be copied. This is because the layout of the shared React shadow may change if the parent of the shared React shadow causes a layout change.
  • Tree lift: Similar to the initial rendered tree lift.
  • Tree comparison: This step calculates the difference between “previously rendered tree” (T) and “next tree” (T’). The result of the calculation is a change operation to the native view.

In the example above, these operations include: UpdateView(‘Node 3’, {backgroundColor: ‘yellow’})

2.3.3 Mount Phase

  • Tree lift: In this step, the “next tree” is automatically promoted to the “previously rendered tree” so that the correct tree is used for comparison calculations in the next mount phase.
  • View mount: This step performs atomic changes on the corresponding native view. In the example above, only the background color of View 3 is updated to yellow.

2.4 Renderer status update

React is the only owner and the only source of facts for most of the information in the shadow tree. And all React data flows in one direction.

But there is one exception. This exception is a very important mechanism: C++ components can have state that is not directly exposed to JavaScript, so JavaScript (or React) is not the only source of facts. In general, C++ state is only used by complex host components, and most host components do not require this feature.

For example, ScrollView uses this mechanism to let the renderer know what the current offset is. Updates to offsets are triggered by the host platform, specifically the ScrollView component. This offset information is useful in apis such as the React Native Measure. Because the offset data is held by C++ state, it is derived from host platform updates and does not affect the React element tree.

Conceptually, C++ state updates are similar to the React state updates we mentioned earlier, but with two differences:

  • Because React is not involved, the Render phase is skipped.
  • Updates can originate and occur in any thread, including the main thread.

Commit Phase: when a C++ status update is performed, a piece of code sets the C++ status of the shadow node (N) to the value S. The React Native renderer will repeatedly try to get the latest committed version of N, copy it with the new state S, and commit the new shadow node N’ to the shadow tree. If React performs another commit during this time, or some other C++ state is updated, the C++ state commit fails. At this point, the renderer will retry the C++ status update several times until the submission is successful, which prevents collisions and contention from real sources.

The Mount Phase is actually the same as the Mount Phase for React status updates. The renderer still needs to recalculate the layout, perform tree comparisons, and so on.

Third, cross-platform implementation

In the React Native renderer, the React shadow tree, layout logic, and view flatting algorithms were implemented separately on each platform. The current renderer design is a cross-platform solution that shares the core C++ implementation. The Fabric renderer uses C++ core rendering directly for cross-platform sharing.

Using C++ as a core rendering system has several advantages.

  • A single implementation reduces development and maintenance costs.
  • It improves the performance of creating React shadow trees. On Android, it also reduces the overhead of the Yoga rendering engine by eliminating the use of JNI for Yoga and improves the performance of layout calculations.
  • Each React shadow node takes up less memory in C++ than in Kotlin or Swift.

The React Native team also uses a mandatory immutable C++ feature to ensure that concurrent access to shared resources is not a problem, even if unlocked. But there are two exceptions on Android where renderers still have JNI overhead:

  • Complex views, such as Text, TextInput, and so on, still use JNI to transport properties.
  • JNI is still used to send change operations during the mount phase.

The React Native team is exploring a new mechanism for serializing data using ByteBuffer instead of ReadableMap to reduce JNI overhead. The goal is to reduce JNI overhead by 35 to 50 percent.

The renderer provides an API for C++ to communicate with both sides:

  • Communicate with the React
  • Communicate with the host platform

React communicates with the renderer, including the render tree and listener events, such as onLayout, onKeyPress, Touch, etc. The React Native renderer communicates with the host platform, including mounting host views on the screen, including create, INSERT, Update, and DELETE host views, and listening for events generated by the user on the host platform.

Four, the view shot flat

A View Flattening is a React Native renderer’s way of optimizing a layout to avoid nesting it too deeply. The React API is designed to enable component declaration and reuse in a composite manner, which provides a good model for simpler development. However, in implementation, these features of the API result in some React elements being deeply nested, and most of these React element nodes only affect the view layout and do not render anything on the screen. This is known as a “layout only” type of node.

Conceptually, there should be a 1:1 relationship between the number of nodes in the React element tree and the number of views on the screen. However, rendering a deep “layout-only” React element can slow performance. Let’s say we have an application that has a ContainerComponent for the margin ContainerComponent. The children of the ContainerComponent are the TitleComponent TitleComponent, which contains an image and a line of text. The React code is provided as follows:

function MyComponent() { return ( <View> // ReactAppComponent <View style={{margin: 10}} /> // ContainerComponent <View style={{margin: 10}}> // TitleComponent <Image {... } /> <Text {... }>This is a title</Text> </View> </View> </View> ); }Copy the code

React Native generates the following three trees when rendering:Views 2 and 3 are “layout-only” views because they render on the screen only to provide a 10 pixel margin.

To improve the performance of the “layout only” type in the React element tree, the renderer implements a view flatten mechanism to merge or flatten such nodes, reducing the hierarchical depth of the host view on the screen. The algorithm takes into account properties such as margin, padding, backgroundColor and opacity.

The view flatting algorithm is part of the Renderer’s Diffing phase, and the advantage of this design is that we don’t need extra CUP time to flatten the “layout-only” views in the React element tree. In addition, as part of the C++ core, the view flattening algorithm is shared by all platforms by default.

In the previous example, views 2 and 3 are flattened as part of the “diffing algorithm,” and their style results are merged into View 1.

However, while this optimization allows the renderer to create and render fewer two host views, it makes no difference to the screen content from the user’s perspective.

5. Thread model

The React Native renderer is thread-safe. From a higher perspective, thread safety within the framework is ensured by immutable data results using the const frist feature of C++. This means that every update to React in the renderer recreates or copies new objects, rather than updating the existing data structures. This is the premise for the framework to expose its thread-safety and synchronization APIS to React.

In React Native, the renderer uses three different threads:

  • UI thread: The only thread that can manipulate the host view.
  • JavaScript thread: This is where the React render phase is performed.
  • Background thread: A thread dedicated to layout.

The React Native rendering process is shown below:

5.1 Rendering a Scene

Render in background threads

This is the most common scenario where most of the rendering pipeline takes place in JavaScript threads and background threads.

Render in the main thread

When there are high-priority events on the UI thread, the renderer is able to execute all the rendering pipelines synchronously on the UI thread.

Default or continuous event interrupts

In this scenario, a low-priority event from the UI thread interrupts the rendering step. React and React Native renderers can interrupt the render step and merge its state with a low-priority event executed in the UI thread. In this case the rendering process continues in the background thread.

The interruption of irrelevant events

The render step is interruptible. In this scenario, a high-priority event from the UI thread interrupts the rendering step. React and the renderer are able to interrupt the render step and merge its state with higher-priority events executed by the UI thread. The UI thread rendering step is performed synchronously.

Background threads from JavaScript threads update in bulk

Before the background thread dispatches updates to the UI thread, it checks to see if any new updates are coming from JavaScript. This way, when the renderer knows that a new state is coming, it will not directly render the old state.

C++ status update

Updates come from the UI thread and skip the rendering step.