React Native Architecture

Aksharmeet SinghAksharmeet Singh
11 min read

In this blog, we'll take a closer look at React Native's inner workings, which enables us to develop native applications for multiple platforms using a single codebase. React Native is continuously evolving, with improvements resulting in a better development experience and more robust applications.

Before we move on to the newer [Fabric] architecture, let's learn a bit about the original architecture

There are four major components or concepts that together form React Native's original architecture:

  1. React

  2. Javascript

  3. Bridge

  4. Native Side

① React

React is a JavaScript library used for building user interfaces.

It uses a component-based architecture, where each component represents a piece of the user interface.

Components can be combined and nested together to build complex user interfaces.

React uses a declarative syntax to define the components and their behaviour. This means that you describe what you want your UI to look like, and React updates the actual UI based on changes in your data or application state.

If you are a beginner with React, I would prompt you to check out the documentation to learn more: https://react.dev/learn

Some of you might be wondering what is the difference between React and React Native.

React is commonly used in web development.

React is a JavaScript library that allows developers to build user interfaces for web and mobile applications. The core library of React is the same for both React and React Native, and it enables developers to create reusable components that can be used across different platforms.

However, there are differences between React and React Native. React uses ReactDOM to render components to the browser's DOM, whereas React Native uses a different rendering engine to create native mobile apps. This means that the components used in React and React Native are different. React components are designed to be used in web applications, while React Native components are designed specifically for native applications.

② Javascript

The React is interpreted to plain javascript, which is then used to communicate with Bridge.

③ Bridge

Bridge in React Native is responsible for marshalling data between the JavaScript and native sides of the app and ensuring that updates to the UI are propagated correctly.

This communication happens asynchronously and is optimized to minimize latency and overhead.

The React Native bridge is a key component of the framework that enables the communication between the JavaScript code running on the device and the native platform code written in Java or Objective-C/Swift. The Bridge handles the transfer of data, events, and function calls between these two sides of the app.

Three important concepts related to the React Native bridge are shadow tree, JSON, and native modules:

⒊1 Shadow Tree

The host operating system has its layout implementation and does not follow the kind of flex box code we just wrote. Therefore, ReactNative first has to convert your flex box-coded layout into a layout system which you're host operating system can understand.

But before doing that, we need to offload this layout calculation part to another thread so we can keep executing our JavaScript thread.

Hence, ReactNative uses the Shadow Thread to construct the Shadow Tree for the layout you coded in your Javascript thread. In this thread, RectNative uses a layout engine called Yoga which converts the flex box-based layout into a layout system your native host can understand.

React Native uses Bridge to communicate this information from the Javascript thread to the Shadow thread.

At this point, we're in the Shadow thread. The Javascript thread is executing, and there's nothing drawn on the screen. When the layout is ready, the shadow thread sends a message to the main UI thread through the Bridge, which contains information about the layout, such as the position and size of the component. The main UI thread then uses this information to render the component on the screen.

⒊2 JSON

JSON (JavaScript Object Notation) is a lightweight data-interchange format that is commonly used in web and mobile development. In React Native, JSON is used as the primary format for data transfer between the JavaScript and native sides of the app via the Bridge. JSON data is serialized and deserialized automatically by the Bridge, making it easy to pass data back and forth without worrying about the underlying details.

⒊3 Native Modules

Native modules are a way to extend the functionality of React Native by writing platform-specific code in Java or Objective-C/Swift and exposing it to the JavaScript side of the app via the Bridge.

In other words, native modules are used to access device-specific functionality that is not available in JavaScript, such as accessing the camera, accelerometer, or GPS. Native modules can also be used to interact with third-party libraries that are not available in JavaScript or to perform low-level operations that require direct interaction with the native platform code.

④ Native Side

The native side is responsible for rendering the user interface using platform-specific native components and APIs, such as UIKit on iOS or Android's native UI components. It also handles platform-specific functionality, such as accessing device hardware like the camera or accelerometer or interacting with third-party libraries that may not be available in JavaScript.


How they all work together

In the React Native architecture, the native side is connected to the JavaScript side via the React Native bridge. When a React component needs to access a native API or function, it sends a message over the Bridge to the native side, executing the requested operation and sending the result back to the JavaScript side.

The key aspect of the initial architecture is that JavaScript and Native realms lack awareness of each other's existence. This means that they operate independently and have no direct way of communicating with each other. Instead, they depend on asynchronous JSON messages transmitted over The Bridge to convey information. These messages are sent with the expectation that the other realm will be aware of them and eventually respond, but there is no guarantee of a timely response or even a response.

According to React Native's official docs. The Bridge had some intrinsic limitations:

  1. It was asynchronous: one layer submitted the data to the Bridge and asynchronously "waited" for the other layer to process them, even when this was unnecessary.

  2. It was single-threaded: JS used to work on a single thread; therefore, the computation in that world had to be performed on that single thread.

  3. It imposed extra overheads: every time one layer used the other one, it had to serialize some data. The other layer had to deserialize them. The chosen format was JSON for its simplicity and human readability, but despite being lightweight, it was a cost to pay.


New Architecture [Fabric]

Fabric is a new architecture that has been gaining attention in the web development community due to its unique approach to improving web application performance and efficiency. It is designed to prioritize user interactions, maintain consistent data and DOM hierarchy, and reduce memory consumption, making it a promising solution for modern web development.

These are three main principles of Fabric:

  1. Task Prioritization: In traditional JavaScript, all async events or activities are treated equally, regardless of their priority level. However, in Fabric, user interactions like scrolling, touch, hold, and gestures are given high priority and executed synchronously in the main or native thread. Tasks such as API requests are executed asynchronously and given low priority.

  2. Immutable Shadow Tree: To maintain consistent data and DOM hierarchy the Shadow Tree must be immutable, as any thread can request a change. This ensures that the tree remains consistent with all other threads it is shared with and avoids any deadlock conditions, regardless of synchronous or asynchronous requests.

  3. Reduced Memory Consumption: The current architecture has two hierarchies or DOM nodes, one in the Shadow thread and the other in the JavaScript thread, which takes up a lot of memory. To reduce memory consumption, Fabric introduces the concept of keeping a single copy of the hierarchy in memory while the other threads only reference it for operations.


Pillars of React Native Fabric Architecture

*CodeGen

According to React Native documentation, CodeGen is not a proper pillar, but it is a tool that can be used to avoid writing a lot of repetitive code. Using CodeGen is not mandatory: all the code generated by it can also be written manually.

It automatically generates some basic code that sets up the project structure and provides some pre-defined components and modules.

This code is referred to as "scaffolding code" because it forms the basic framework or structure of the project, which you can build upon to create your app.

The CodeGen is invoked automatically by React Native every time an iOS or Android app is built.

The purpose of the CodeGen tool in React Native is to automate the generation of type-safe interfaces for native modules. In addition, it is designed to scaffold the necessary code for the interface, making it easier for developers to interact with native modules in a type-safe manner.

Now you may ask why we even need to worry about ensuring type-safe interaction with native modules in React Native apps. When developers use these modules to access platform-specific functionalities, such as a camera or file system access, there is often no guarantee that the data being passed to and from the module is of the correct type. This lack of type safety can lead to errors and bugs in the application that are difficult to diagnose and fix. The CodeGen tool is designed to address this problem by generating TypeScript interfaces for native modules, which ensures type safety during interaction with the module.

*JSI

JSI replaced the Bridge from the current architecture.

The JSI provides API to the JS Runtime engine(where js code is executed when you run it) and makes JS "aware" of the native functions and objects directly, without any bridge.

*"*by using JSI, JavaScript can hold a reference to C++ Host Objects and invoke methods on them."

Holding a reference to a C++ host object and invoking methods through JSI allows JavaScript code to directly interact with native code, bypassing the need for a bridge that converts data between the two languages.

By enabling JavaScript to directly call C++ code, JSI makes it possible to build complex, high-performance applications that leverage the strengths of both languages.

*Fabric

The group of elements responsible for managing the React Native UI's appearance and interaction with the native side are the Shadow Tree and Native Modules. Communication between the two happens via asynchronous JSON messages that are batched and sent over one channel, which can result in congestion and suboptimal performance. To address this issue, Facebook split the Bridge into two separate actors: Fabric, which re-architects the UI manager, and TurboModules, which is the new generation implementation of interaction with the native side.

The introduction of Fabric aims to modernize React Native's rendering layer. The current implementation uses a series of cross-bridge "steps" to handle all UI operations (React -> Native -> Shadow Tree -> Native UI). In contrast, Fabric allows the UI manager to create the Shadow Tree directly in C++, reducing the number of "jumps" across realms and greatly increasing the swiftness of the process. This enhancement greatly improves UI responsiveness.

Furthermore, Fabric exposes UI operations to JavaScript as functions through JSI. The new Shadow Tree, which determines what is shown on the screen, is shared between the two realms, enabling direct interaction from both ends.

*Turbo Modules

Turbo Native Modules are the next version of Native Modules, which offer a few additional benefits, as described on the React Native documentation page.

In the existing implementation, Native Modules utilized by JavaScript code, like Bluetooth, must be initialized when the app is launched, even when they're not being used, because of the lack of communication between the native and javascript thread causing the "unawareness".

The new TurboModules approach permits the JavaScript code to load each module only when required, and to have direct access to it, eliminating the need for batched JSON message communication on the old Bridge.

This will greatly enhance startup time for applications with numerous Native Modules, as well as the direct communication mentioned in previous articles,

  • Strongly typed interfaces that are consistent across platforms

  • The ability to write your code in C++, either exclusively or integrated with another native platform language, reducing the need to duplicate implementations across platforms

  • Lazy loading of modules, allowing for faster app startup

  • The use of JSI, a JavaScript interface for native code, allows for more efficient communication between native and JavaScript code than the Bridge


The flow of new architecture

how it all works together

  1. The user clicks on the app icon.

  2. Fabric directly loads the native side (no native modules).

  3. It tells the JS thread that it is ready, and now the JS side loads all the main.bundle.js, which contains all js and react logic + components.

  4. JS is called through the ref native function (the one exposed as an object using the JSI API) to Fabric, and the shadow node creates the tree as before.

  5. Yoga does the layout calculation converting from a flexbox-based style to a host layout.

  6. Fabric does its thing and shows the UI.


Benefits of Fabric Architecture

This architecture allowed the unlocking of several benefits

  • Synchronous execution: it is now possible to execute synchronously those functions that should not have been asynchronous in the first place.

  • Concurrency: it is possible from JavaScript to invoke functions that are executed on different threads.

  • Lower overhead: the New Architecture doesn't have to serialize/deserialize the data; therefore, there are no serialization taxes to pay.

  • Code sharing: by introducing C++, it is now possible to abstract all the platform-agnostic code and easily share it between the platforms.

  • Type safety: A layer of code automatically generated has been added to ensure that JS can properly invoke methods on C++ objects and vice-versa. The code is generated from some JS specification that must be typed through Flow or TypeScript.]


Conclusion

If you were able to stick to the end then you would now have a better understanding of how React Native Fabric Architecture works.

1
Subscribe to my newsletter

Read articles from Aksharmeet Singh directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aksharmeet Singh
Aksharmeet Singh