DEV Community

Cover image for Exploring React Native’s new architecture
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Exploring React Native’s new architecture

Written by David Ekanem✏️

In this article, we’ll explore the upcoming React Native version and the changes to the React Native architecture.

Let’s jump right into it.

What’s happening to the React Native architecture?

A key architectural change coming to React Native is the new, native TurboModule system and the Fabric Renderer.

In the next sections, we’ll examine how these key architectural changes impact working with React Native applications by upgrading the Android Gradle plugin to be compatible with the latest nightly release version of React Native. We’ll specifically cover:

Preparing an Android app for the new React Native architecture

For our Android application, the following prerequisites must be met:

  • Gradle v 7.x and Android Gradle plugin (AGP) v 7.x
  • The new React Gradle plugin
  • Building react-native from Source.
  • Node.js ≥ v 14

The taste of the pudding is in the eating, so with that little quip, let’s get our hands dirty by bootstrapping a new React Native application.

npx react-native init reactNativeNew
Enter fullscreen mode Exit fullscreen mode

Once that is done, it’s time to focus on getting our application ready to receive the nightly release of the latest version of React Native.

Upgrading our Gradle

cd android && ./gradlew wrapper -gradle-version 7.3 -distribution-type=all
Enter fullscreen mode Exit fullscreen mode

The AGP version updates the top-level build.gradle file.

Install the new Gradle plugin:

cd ..
yarn add react-native-gradle-plugin
code android/settings.gradle
Enter fullscreen mode Exit fullscreen mode

Edit the settings.gradle file to include the following lines at the end of the file:

includeBuild('../node_modules/react-native-gradle-plugin')

if (settings.hasProperty("newArchEnabled") && settings.newArchEnabled == "true") {
    include(":ReactAndroid")
    project(":ReactAndroid").projectDir = file('../node_modules/react-native/ReactAndroid')
}
Enter fullscreen mode Exit fullscreen mode

Adding the Gradle plugin to our build script:

code android/build.gradle
Enter fullscreen mode Exit fullscreen mode

Now, add the following code block:

buildscript {
    dependencies {
        classpath("com.android.tools.build:gradle:7.0.4")
        classpath("com.facebook.react:react-native-gradle-plugin")
        classpath("de.undercouch:gradle-download-task:4.1.2")
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
Enter fullscreen mode Exit fullscreen mode

Edit the module-level Gradle file like so:

code android/app/build.Gradle
Enter fullscreen mode Exit fullscreen mode

And now, add the following code block at the top level:

apply plugin: "com.android.application"

import com.android.build.OutputFile

// Add those lines
apply plugin: "com.facebook.react"
// Add those lines as well
react {
    reactRoot = rootProject.file("../node_modules/react-native/")
    codegenDir = rootProject.file("../node_modules/react-native-codegen/")
}
Enter fullscreen mode Exit fullscreen mode

In the dependencies section of the /android/app/build.gradle file, add the below code:

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])

    //noinspection GradleDynamicVersion
    // replace this
    implementation "com.facebook.react:react-native:+"  // From node_modules

    // with this
     implementation project(":ReactAndroid")  // From node_modules
Enter fullscreen mode Exit fullscreen mode

Accessing the latest version of React Native

In order to access the most up-to-date changes to React Native, it is expected that the target application makes use of a specific nightly release. Before upgrading the app to a specific nightly release, it is recommended to first upgrade the application to the latest open source release.

yarn add react-native
Enter fullscreen mode Exit fullscreen mode

Once the application is upgraded to the latest open source release successfully, we can target the nightly release. The below command helps with that.

yarn add react-native@0.0.0-20220201-2008-79975d146
Enter fullscreen mode Exit fullscreen mode

Install the latest version of react-native-codegen, which is a tool for automating the compatibility between JavaScript and native code at build time, instead of at runtime.

yarn add react-native-codegen
Enter fullscreen mode Exit fullscreen mode

With our Android application up to date with the latest version of React Native, it’s time to explore the new renderer system: Fabric.

Exploring Fabric, React Native’s new rendering system

Fabric is React Native’s new rendering system. The Fabric renderer seeks to improve the interoperability of React Native with host platforms, which are responsible for embedding React Native in Android, iOS, macOS, Windows, etc.

Improved communication between JavaScript and the native threads

In the current architecture, when a React Native application is run, the JavaScript code is bundled together into a package called JS Bundle, and the native code is kept separate. The JavaScript thread runs the JS Bundle, and the native/UI thread runs the native modules and handles UI rendering. The communication between the JS and native threads is enabled by a bridge, which sends data to the native threads after serializing as JSON. This bridge can only handle asynchronous communication.

With Fabric, logic is rendered in C++, which improves the interoperability between the host platforms. The Fabric renderer is implemented in C++, and the C++ core is shared among platforms, providing greater consistency and making React Native easier to adopt on new platforms.

Furthermore, the new architecture decouples the JavaScript interface from the engine, enabling the use of other JavaScript engines such as Hermes, V8, or Chakra.

Improved interoperability between React Native and host views

A host view is a tree representation of views in the host platform (e.g., Android, iOS).

Tree Representation Of Views In The Host Platform

Source: reactnative.dev/

The improved interoperability between React Native and host views is enabled by the C++ core, which is shared among different host platforms and enables React Native to render React surfaces synchronously.

It wasn’t always this way — in the legacy React Native architecture, the layout was asynchronous, which led to a layout “jump” issue when embedding a React Native rendered view in a host view.

Improved Interoperability Between React Native And Host Views

Source: React Navigation GitHub issue #6996

Improvements to data fetching

Data fetching in applications is now much more intuitive, thanks to the integration with React Suspense. Other new features available in React are now enabled in the Fabric renderer, such as the new Concurrent Features available in React 18. This includes the startTransition feature, which keeps the UI of our applications responsive during expensive state transitions.

Server-side rendering is also made easier with the new renderer.

The three phases of the Fabric render pipeline

The render pipeline occurs in three phases:

  1. The Render phase
  2. The Commit phase
  3. The Mount phase

Let’s look into each of them more closely.

The Render phase

In this phase, React executes product logic to create React element trees, which consist of React elements. A React Element is a plain JavaScript object that describes what should appear on the application screen.

The React element tree is used to render the React shadow tree in C++. The React Shadow Tree is created by the Fabric Renderer and consists of React shadow nodes, which are objects that represent the React host components that are to be mounted, and contains props that originate from JavaScript.

const App = () => {
  return (
    <View>
      <Text>App.js</Text>
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

In the Render phase, as each React element is invoked, the renderer synchronously creates a React shadow node. Take note that this synchronous creation of a React shadow node only occurs for React host oomponents, and not for React composite components. When translated into a React Shadow Block, the above code would see the <View> translated into a ViewShadowNode object.

A great thing about the new renderer is that any parent-child relationship between React element nodes will correspond to the relationships among React shadow nodes. The above process shows how the React shadow tree is assembled; once the React shadow tree is complete, the renderer triggers a commit of the React element tree.

Below is a visual representation of the render phase:

Visual Representation Of The Render Phase

Source: reactnative.dev

The Commit phase

The cross-platform layout engine Yoga is hugely important in handling operations that happen during the commit phase, which consists of two operations: layout calculation and tree promotion.

The layout calculation calculates the position and size of each React shadow node. This is achieved by invoking Yoga to calculate the layout of each React shadow node.

Once the calculation determines the amount of available space, the Tree Promotion operation promotes the new React shadow tree as the next tree to be mounted. This promotion represents the latest state of the React element tree.

The Commit Phase Diagram

Source: reactnative.dev

The Mount phase

This is the phase in which the React Shadow Tree (which contains the data from the layout calculation) is transformed into a host view tree with rendered pixels on the screen. The Fabric renderer creates a corresponding host view for each React shadow node and mounts it on screen.

The Mount Phase Diagram

Sources: reactnative.dev

TurboModules

The TurboModules system is an enhancement of Native Modules. In their current architecture, a table holds the module registry, and when the JavaScript code calls a specific native module, the indices of the module and the methods are passed to Java/ObjC, which invoke the specific methods.

A proposed solution to the eager initialization of Native packages is the use of the LazyReactPackage, but this is not an effective method because the annotation processor ReactModuleSpecProcessor does not run with Gradle; hence, the LazyReactPackage doesn’t work with the open source release.

With the new implementation, JavaScript will expose a top level Native Module Proxy, called global.__turboModuleProxy, to access a native module. If we used the example of "SampleTurboModule", the application code will call the require('NativeSampleTurboModule'). In the NativeSampleTurboModule, the TurboModuleRegistry.getEnforcing() function is called, which holds a reference to the Native Modules and then calls global.__turboModuleProxy("SampleTurboModule").

This triggers the JSI function and a getModule function is invoked, which is defined for Java and ObjC, and returns a JSI object for the specific TurboModule (i.e., GeoLocation, FileStorage, DeviceInformation) that previously would have been initialized at app startup. This allows JavaScript code to load each module when it is required, instead of initializing them before the app is opened.

Summary

The React Native next version promises an improvement in startup time, and developer experience, with improved interoperability between all threads, a new web-like rendering system(Fabric). A key aspect that excites me about the new architecture, is the ability to easily build applications for Smart TVs.


LogRocket: Instantly recreate issues in your React Native apps.

LogRocket React Native monitoring solution

LogRocket is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.

LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.

Start proactively monitoring your React Native apps — try LogRocket for free.

Discussion (0)