React Made Native Easy

  Edit

React Native Internals

React Native is a framework which allows developers to build native apps using Javascript. Wait! Cordova already does that and it has been around for quite a while. Why would anyone want to use RN?

The primary difference between RN and Cordova based apps is that Cordova based apps run inside a webview while RN apps render using native views. RN apps have direct access to all the Native APIs and views offered by the underlying mobile OS. Thus, RN apps have the same feel and performance as that of a native application.

At first, it is easy to assume that React Native might be compiling JS code into respective native code directly. But this would be really hard to achieve since Java and Objective C are strongly typed languages while Javascript is not! Instead, RN does something much more clever. Essentially, React Native can be considered as a set of React components, where each component represents the corresponding native views and components. For example, a native TextInput will have a corresponding RN component which can be directly imported into the JS code and used like any other React component. Hence, the developer will be writing the code just like for any other React web app but the output will be a native application.

Ok! This looks like black magic 🙄.

To understand this, let us take a look at the architecture and how React Native works internally.

Architecture 🤖

Both iOS and Android have a similar architecture with subtle differences.

If we consider the big picture, there are three parts to the RN platform:

  1. Native Code/Modules: Most of the native code in case of iOS is written in Objective C or Swift, while in the case of Android it is written in Java. But for writing our React Native app, we would hardly ever need to write native code for iOS or Android.

  2. Javascript VM: The JS Virtual Machine that runs all our JavaScript code. On iOS/Android simulators and devices React Native uses JavaScriptCore, which is the JavaScript engine that powers Safari. JavaScriptCore is an open source JavaScript engine originally built for WebKit. In case of iOS, React Native uses the JavaScriptCore provided by the iOS platform. It was first introduced in iOS 7 along with OS X Mavericks.
    https://developer.apple.com/reference/javascriptcore.

In case of Android, React Native bundles the JavaScriptCore along with the application. This increases the app size. Hence the Hello World application of RN would take around 3 to 4 megabytes for Android.

In case of Chrome debugging mode, the JavaScript code runs within Chrome itself (instead of the JavaScriptCore on the device) and communicates with native code via WebSocket. Here, it will use the V8 engine. This allows us to see a lot of information on the Chrome debugging tools like network requests, console logs, etc. 😎

  1. React Native Bridge: React Native bridge is a C++/Java bridge which is responsible for communication between the native and Javascript thread. A custom protocol is used for message passing.

react native architecture diagram

In most cases, a developer would write the entire React Native application in Javascript. To run the application one of the following commands are issued via the CLI - react-native run-ios or react-native run-android. At this point, React Native CLI would spawn a node packager/bundler that would bundle the JS code into a single main.bundle.js file. The packager can be considered as being similar to Webpack. Now, whenever the React Native app is launched, the first item to be loaded is the native entry point. The Native thread spawns the JS VM thread which runs the bundled JS code. The JS code has all the business logic of the application. The Native thread now sends messages via the RN Bridge to start the JS application. Now, the spawned Javascript thread starts issuing instructions to the native thread via the RN Bridge. The instructions include what views to load, what information is to be retrieved from the hardware, etc. For example, if the JS thread wants a view and text to be created it will batch the request into a single message and send it across to the Native thread to render them.

[ [2,3,[2,'Text',{...}]] [2,3,[3,'View',{...}]] ]

The native thread will perform these operations and send the result back to the JS assuring that the operations have been performed.

Note: To see the bridge messages on the console, just put the following snippet onto the index.<platform>.js file

import MessageQueue from 'react-native/Libraries/BatchedBridge/MessageQueue';
MessageQueue.spy(true);

Threading Model 🚧

When a React Native application is launched, it spawns up the following threading queues.

  1. Main thread (Native Queue) - This is the main thread which gets spawned as soon as the application launches. It loads the app and starts the JS thread to execute the Javascript code. The native thread also listens to the UI events like 'press', 'touch', etc. These events are then passed to the JS thread via the RN Bridge. Once the Javascript loads, the JS thread sends the information on what needs to be rendered onto the screen. This information is used by a shadow node thread to compute the layouts. The shadow thread is basically like a mathematical engine which finally decides on how to compute the view positions. These instructions are then passed back to the main thread to render the view.

  2. Javascript thread (JS Queue) - The Javascript Queue is the thread queue where main bundled JS thread runs. The JS thread runs all the business logic, i.e., the code we write in React Native.

  3. Custom Native Modules - Apart from the threads spawned by React Native, we can also spawn threads on the custom native modules we build to speed up the performance of the application. For example - Animations are handled in React Native by a separate native thread to offload the work from the JS thread.

Links: https://www.youtube.com/watch?v=0MlT74erp60

View Managers 👓

View Manager is a native module that maps JSX Views onto Native Views. For example:

import React, { Component } from "react";
import { Text, View, AppRegistry } from "react-native";

class HelloWorldApp extends Component {
  render() {
    return (
      <View style={{ padding: 40 }}>
        <Text>Hello world!</Text>
      </View>
    );
  }
}

export default HelloWorldApp;
AppRegistry.registerComponent("HelloWorldApp", () => HelloWorldApp);

Here when we write <Text />, the Text View manager will invoke new TextView(getContext()) in case of Android.

View Managers are basically classes extended from ViewManager class in Android and subclasses of RCTViewManager in iOS.

Development mode 🔨

When the app is run in DEV mode, the Javascript thread is spawned on the development machine. Even though the JS code is running on a more powerful machine as compared to a phone, you will soon notice that the performance is considerably lower as compared to when running in bundled mode or production mode. This is unavoidable because a lot more work is done in DEV mode at runtime to provide good warnings and error messages, such as validating propTypes and various other assertions. Furthermore, the latency of communication between the device and the JS thread also comes into play.

Link: https://www.youtube.com/watch?v=8N4f4h6SThc - RN android architecture

React Native's new Architecture (Fabric)

React Native team is currently working on a new architecture for React Native. The new architecture is codenamed Fabric and would allow React Native to perform high priority UI updates in a synchronous manner. This means the UI would be more responsive in certain edge cases such as scroll view. To learn about what Fabric is and how it will improve React Native experience watch this great talk by Parashuram N at React Conf 2018.