Skip to main content

React Native Architecture Evolution

Free2019-09-14#Tool#React Native new Architecture#React Native 新架构#React Native Fabric#React Native 重构#React Native rearchitect

What problems existed in React Native's original design, and how does it plan to improve?

Preface

The previous article (React Native Architecture Overview) introduced React Native's existing architecture from aspects such as design and thread models. This article will analyze the limitations of this architecture and the architecture upgrade plan that React Native is currently undergoing.

1. Limitations of Existing Architecture

The original design also brought some restrictions:

  • Asynchronous: Cannot directly integrate JavaScript logic with many Native APIs that require synchronous answers

  • Batch processing: Difficult to let React Native applications call Native-implemented functions

  • Serializable: Unnecessary copying exists instead of directly sharing memory

These problems are especially prominent in Native + React Native hybrid applications:

For apps that are entirely built in React Native, these restrictions are usually bearable. But for apps with complex integration between React Native and existing app code, they are frustrating.

2. Architecture Upgrade Plan

Therefore, in June 2018, a large-scale refactoring plan was proposed to better support hybrid applications:

We're working on a large-scale rearchitecture of React Native to make the framework more flexible and integrate better with native infrastructure in hybrid JavaScript/native apps.

Specifically, there are 3 major changes:

  • Thread model: Allows synchronously calling JavaScript execution in any thread for high-priority updates, UI updates no longer need to cross 3 threads

  • React: Supports new features of React 16+, including async rendering, Data Fetching, etc.

  • Bridge: Streamlined and optimized, allowing direct calls between Native and JavaScript

Supporting synchronous calls makes some things that were previously difficult to implement possible, such as cross-language call stack tracing

Corresponding to the architecture diagram, it's equivalent to optimizing each layer separately:

  • React layer: Enhance JavaScript type safety and support React 16+ new features

  • JavaScript layer: Introduce JSI, allowing replacement of different JavaScript engines

  • Bridge layer: Divided into Fabric and TurboModules, responsible for UI rendering and Native modules respectively

  • Native layer: Streamline core modules, split non-core parts as community modules for independent update and maintenance

Preliminary estimates suggest these refactoring work is expected to be completed around the end of 2019 or early 2020:

It's likely this massive piece of work will reach its conclusion around Q4 2019 or Q1 2020, but there are no confirmed dates.

P.S. Currently (2019/9/8), except for the completed JSI, the rest of the refactoring plans are still in progress, see The New React Native Architecture Explained: Part Four for details

3. Enhance JavaScript Type Safety

The main change is providing CodeGen tools to guarantee type safety of message communication, to solve the Bridge API data type problems widely criticized in JavaScript and Native communication:

We also experienced many issues in which the types coming from JavaScript were unexpected. For example, integers were often wrapped by strings, an issue that isn't realized until it is passed over a bridge. To make matters worse, sometimes iOS will fail silently while Android will crash.

(From React Native at Airbnb: The Technology)

On the other hand, type constraints also help communication performance:

This automation will speed up the communication too, as it's not necessary to validate the data every time.

4. Introduce JSI

Upper JavaScript code needs a runtime environment. In React Native, this environment is JSC (JavaScriptCore). Different from directly inputting JavaScript code to JSC before, the new architecture introduces a layer of JSI (JavaScript Interface) as an abstraction above JSC, used to shield JavaScript engine differences, allowing switching to different JavaScript engines (such as the recently launched Hermes)

More importantly, with JSI, JavaScript can also hold references to C++ objects and call their methods:

By using JSI, JavaScript can hold reference to C++ Host Objects and invoke methods on them.

Thus allowing direct calls between JavaScript and Native, without going through cross-thread message communication, saving serialization/deserialization costs, and can also reduce Bridge communication pressure (such as大量 message queueing traffic jams)

At the same time, the C++ layer where JSI resides can also serve as a way to reuse Native code, with Native's natural support:

  • Android: Call C or C++ modules through JNI (Java Native Interface)

  • iOS: Objective-C supports by default

5. Refactor Bridge Layer

The new Bridge layer is divided into two parts: Fabric and TurboModules:

  • Fabric: Responsible for managing UI

  • TurboModules: Responsible for interacting with Native

Fabric expects to implement React Native's rendering layer in a more modern way, simplifying the complex cross-thread interactions in the previous rendering process (React -> Native -> Shadow Tree -> Native UI). Specifically, directly create a Shadow Tree shared by JavaScript and Native at the C++ layer, and expose UI operation interfaces to JavaScript through the JSI layer, allowing JavaScript to directly control high-priority UI operations, even allowing synchronous calls (to handle scenarios like fast list scrolling, page switching, gesture processing, etc.)

Previously, all Native Modules (whether needed or not) had to be initialized at application startup, because Native didn't know which functional modules JavaScript would call. The new TurboModules allows on-demand loading of Native modules, and directly holds their references after module initialization, no longer relying on message communication to call module functions. Therefore, application startup time will also improve

6. Streamline Core Modules

Theoretically, React Native should be universal and platform-agnostic, which is key to supporting different platforms like Web, Windows, etc.

Although Native is not under React Native's control and cannot be vertically deeply optimized, horizontal streamlining can be done, splitting non-core code parts as community modules, such as AsyncStorage, ImageStore, MaskedViewIOS, NetInfo, etc. On one hand reducing package size, on the other hand also beneficial for independent update and maintenance of these modules

References

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment