Wait… What Happens When my React Native Application Starts? — An In-depth Look Inside React Native
Discover how React Native functions internally, and what it does for you without you knowing it.
Disclaimer: this articles assumes a (very) basic understanding of React Native and Native Modules. If you have never played around with them, I’d recommend getting a look at the official documentation first.
Edit: This article is based on a talk I’ve given in London for the ReactFestconference in March 2018. You can see me talk over here!
As I started using React Native, something quickly bugged me: what’s happening there? Indeed, from the standpoint of someone who simply uses React Native to build (great) applications, things can sometimes look magical. Want to make some native code usable from Javascript? Simply use @ReactMethod
(or RCT_EXPORT_METHOD
on iOS)! Want to send an event over from native to Javascript? No problem, just grab the appropriate Javascript module and call it like you would do for any native method! Not to mention, the same Javascript code will run on both iOS and Android…
As one might guess, these things are not trivial. Fairly complex, in fact. So what enables React Native to achieve such feats? The easy and commonly given answer is:
The React Native “Bridge”, duh!
In other words:
Some React Native explanations…
But who wants easy answers? This time around we will go into details. But be warned: things might get messy.
The React Native Infrastructure
Let’s get straight to the point: in order to function, React Native relies on a full infrastructure built at runtime. Ta-da!
Wait… didn’t you just give us another cryptic explanation?
I did? But there are two things to note here. First, infrastructure: the famous “bridge” is just a part of it — and not the most incredible, I dare say. Second, runtime: the above infrastructure is being built every single time you launch a React Native application, before any custom code is executed. In other words, before your application comes online and becomes visible, it goes through a transitional state where React Native is busy building your application’s foundations for you.
What does this so-called infrastructure looks like, you say? Well, let us draw a map, showing various parts (linked by arrows roughly meaning “stores a reference to”).
React Native Infrastructure — the Java logo could be replaced with an Objective-C one in the case of iOS
Woops, that got a little complex, didn’t it? And this is a simplified version… In order to understand this mess, we will describe its parts one by one, in the order in which they are created at launch. Let’s go!
Starting a React Native Application
Remember that the whole thing is built whenever a React Native application starts? Let us go through the multiple steps that separate the instant when you press your application’s logo on your phone from the moment everything becomes visible.
First, your application only has two things to work with:
- The application’s code,
- A unique thread, the main thread* , that is automatically assigned to it by the phone’s operating system.
To simplify explanations, we are going to conceptually split the code in two: the framework code — the one you do not have to write every time — and the _custom code _— that actually describes your application. Both being distributed over Javascript and native, this gives us a total of four parts of code to go through. The first thing that will get processed — on the main thread — is the native part of the framework code.
*also called UI Thread_, as — outside of initialization — it will mainly be responsible of UI-related work_
Creating the Native Foundations
One important thing to realize is that, although most of the UI code — <View>
, <Text>
… — is written in Javascript, what will in the end be rendered are only native views. That’s it. This means that the React Native framework needs to:
- create native views and map their connections to Javascript components,
- store these natives views and display them.
While the first step will be handled by the UIManagerModule (which we will describe a little later), the RootView will take care of the second one. The RootView
is more or less a container in which native views are organized in a big tree — a native representation of the Javascript component tree, if you like — and everything that will show up on the phone’s screen will be stored in there.
Back to our initializing process: everything starts with the creation of the above RootView
— an empty container for now — before moving on to the Bridge Interface.
Wasn’t the bridge supposed to be between native and Javascript? Why would you need an interface there?
It is! But although most of the native side — including the RootView
— is written in a platform-specific language (Objective-C or Java), the bridge is entirely implemented in C++ . The Bridge Interface therefore acts as an API, allowing the former to interact with the latter. The bridge itself is made up of two ends, native to Javascript, and vice versa.
The bridge, however, would be nothing without endpoints to dispatch calls to. These endpoints are Native Modules and will, in the end, be the only things available to the Javascript environment. In other words, everything but Native Modules will be eventually invisible to your Javascript application. For this reason, aside from the Custom Modules that you may or may not decide to create, the framework also includes Core Modules. One example of the latter would be the UIManagerModule , which stores a map of all Javascript UI Components and their associated native views. Every time a Javascript UI Component is created, updated or deleted, the UIManagerModule
will use this map to accordingly create, update or delete the corresponding native view. It will also forward changes to the native view tree stored in the RootView
in order to make them visible.
From the standpoint of initialization, all Native Modules are treated the same: for each module, an instance is created, and a reference to that instance is stored on the Javascript to native bridge — so that they can later be called from Javascript. In addition, a reference to the Bridge Interface is also passed to each Native Module, allowing them to call Javascript directly. Finally, two additional threads will be created: the JS Thread and the NativeModulesThread*.
*strictly speaking, it is not a unique thread but a pool of threads in the case of the iOS implementation of React Native.
Intermezzo: Setting Up the Javascript Engine
Before moving on, let’s make a quick summary of what has happened so far:
- a bunch of native stuff has been created on the main thread,
- we now have three threads to work with,
- absolutely no Javascript has been processed yet.
Referring back to our initial map, what we have is this:
React Native’s native side
Meaning it is now time to load the Javascript bundle — framework and custom code alike!
Being an interpreted scripting language, Javascript cannot be run as is: it needs to be converted to bytecode, then be executed. This is the job of the Javascript virtual machine (a.k.a. Javascript engine). There are many Javascript engines out there, including Chrome’s V8, Mozilla’s SpiderMonkey and Safari’s JavaScriptCore… If in debug mode, React Native will use V8 and directly run in the browser, otherwise, it defaults to JavaScriptCore and runs on the device. As a side note, JavaScriptCore is not included in Android by default (while it is in iOS), so React Native automatically bundles a copy of it in the Android application — making Android applications slightly heavier than their iOS counterparts.
In any case, before effectively kicking off the Javascript engine, React Native has to give it a Context representing the execution environment. That includes the Javascript global object, and means that this global object is in fact created and stored on the C++ bridge. Why is that so important? Because the global object is then not only reachable from within the Javascript environment, but also from outside. It is therefore the primary means of communication between C++ (native) and Javascript, as it is through the global object that some native functions will be made available to Javascript — functions that will in turn be used to pass back data from Javascript to native.
Many things are stored on the global object, but the ModuleConfig array and the flushQueue() function are especially important. Each element of the ModuleConfig
array describes a Native Module (be it Core or Custom), including its name, its exported constants, methods… The flushQueue()
function plays a critical role in assuring communication between the Javascript and native environment, as it it will be used periodically to pass calls from the first back to the second.
Once the Javascript Context has been fully created and filled, it is fed to the Javascript engine that starts loading the React Native Javascript bundle on theJS Thread.
Loading the Javascript Bundle
As the virtual machine begins processing the Javascript part of the framework code, it will create the BatchedBridge. That name might ring a bell as it sometimes pops up in error messages! Despite its fancy denomination, it is but a simple queue, that stores “calls from Javascript to native”. A “call” is an object containing a native module ID, a method ID (for the specified native module), along with arguments that the native method is to be called with. Periodically (every 5 milliseconds by default), the BatchedBridge
will call on global.flushQueue()
, passing its content — an array of “calls” — to the Javascript to native end of the C++ bridge. Know as batches , these small arrays are indexed so as to ensure that all UI changes contained in one batch are made visible at the same time (this is necessary because the entire process is asynchronous). The Javascript to native end of the bridge will finally iterate over each call in a batch and dispatch them to the appropriate native module using the specified module ID — which it can do because it has a reference pointing to each and any native module, remember?
The next step is creating the NativeModules
object — yes, the very object that has to be imported from ‘react-native’ each time you want to call a native module. The NativeModules
object will be filled using the ModuleConfig
array mentioned earlier. I will not go into the details of this process here, but it is roughly equivalent to doing NativeModules[module_name]={}
for each module_name
contained in the ModuleConfig
, then NativeModules[module_name][method_name]=fillerMethod
for each exported native method of the given module. fillerMethod
is simply there to store all arguments it receives on the BatchedBridge
, along with the method and module ID (something like fillerMethod = function(...args) { BatchedBridge.enqueueNativeCall(moduleID, methodID, args)}
), effectively creating a “call” from Javascript to native. That being said, what is fired when you later write MyNativeModule.myMethod(args)
is actually the abovefillerMethod
!
We’re almost there. The last thing that needs to be done is creating the core JS Modules, among which the DeviceEventEmitter
— that will be used to send events from native to Javascript — or the AppRegistry
, that stores a reference to the main components of your app. In order to be callable from native, these modules are registered on the Javascript global object…
…and with that, the full React Native infrastructure has been built!
Making the React Native Application Visible
Despite the initialization being all but complete, our application is still invisible at this stage! Indeed, the loading of the Javascript bundle happened on the JS thread, which is independent of the main (a.k.a. UI) thread. The JS thread thus has to warn the main thread about the completion of its task, and in response, the main thread uses the AppRegistry
(JS module) to ask the JS thread to process the main custom component — usually App.js
.
To sum it up from a threading perspective, a React Native application’s launch process looks like this:
React Native’s starting routine
The Javascript component tree contained in your application’s main component will be traversed, calling the UIManagerModule
every time a UI component is encountered. The UIManagerModule
(on the UI thread) will in turn take care of creating native views and storing them in the RootView
: congratulations, your application is now visible!
Wait… What Happens When my React Native Application Starts? — An In-depth Look Inside React Native的更多相关文章
- Native Application 开发详解(直接在程序中调用 ntdll.dll 中的 Native API,有内存小、速度快、安全、API丰富等8大优点)
文章目录: 1. 引子: 2. Native Application Demo 展示: 3. Native Application 简介: 4. Native Ap ...
- 微软推出了Cloud Native Application Bundles和开源ONNX Runtime
微软的Microsoft Connect(); 2018年的开发者大会 对Azure和IoT Edge服务进行了大量更新; Windows Presentation Foundation,Window ...
- react系列笔记1 用npx npm命令创建react app
react系列笔记1 用npx npm命令创建react app create-react-app my-app是开始构建新的 React 单页应用程序的最佳方式.它已经为你设置好了开发环境,以便您可 ...
- 关于React.PropTypes的废除,以及新版本下的react的验证方式
React.PropTypes是React用来typechecking的一个属性.要在组件的props上运行typechecking,可以分配特殊的propTypes属性: class Greetin ...
- react全家桶从0搭建一个完整的react项目(react-router4、redux、redux-saga)
react全家桶从0到1(最新) 本文从零开始,逐步讲解如何用react全家桶搭建一个完整的react项目.文中针对react.webpack.babel.react-route.redux.redu ...
- React实战教程之从零开始手把手教你使用 React 最新特性Hooks API 打造一款计算机知识测验App
项目演示地址 项目演示地址 项目代码结构 前言 React 框架的优雅不言而喻,组件化的编程思想使得React框架开发的项目代码简洁,易懂,但早期 React 类组件的写法略显繁琐.React Hoo ...
- React Native 开发之 (02) 用Sublime 3作为React Native的开发IDE
Sublime Text是一个代码编辑器.也是HTML和散文先进的文本编辑器.漂亮的用户界面和非凡的功能,例如:迷你地图,多选择Python插件,代码段等等.完全可自定义键绑定,菜单和工具栏等等.漂亮 ...
- How to debug Android Native Application with eclipse
This blog is inspired by this tutorial http://mhandroid.wordpress.com/2011/01/23/using-eclipse-for-a ...
- [React] Configure a React & Redux Application For Production Deployment and Deploy to Now
In this lesson, we’ll make a few small changes to our scripts and add some environment variables tha ...
随机推荐
- electron——ipcMain模块、ipcRenderer模块
ipcMain 从 主进程 到 渲染进程 的异步通信. ipcMain模块是EventEmitter类的一个实例. 当在主进程中使用时,它处理从渲染器进程(网页)发送出来的异步和同步信息. 从渲染器进 ...
- X264-libx264编码库
X264编码库libx264实现真正的视频编解码,该编解码算法是基于块的混合编码技术,即帧内/帧间预测,然后对预测值变换.量化,最后熵编码所得. 编码帧的类型分为I帧(x264_type_i).P帧( ...
- 【Linux】扩展阿里云数据盘分区和文件系统
扩容云盘只是扩大存储容量,不会扩容文件系统 一.准备工作 在扩展数据盘扩展分区和文件系统前,请提前完成以下工作. 创建快照以备份数据,防止操作失误导致数据丢失. 通过ECS控制台或者API扩容云盘容量 ...
- java多线程执行时主线程的等待
1.通过thread.join()方式,注意:如果有多个子线程,需要将全部的线程先start,然后再join.代码示例如下: public class Main { public static ...
- Python 报错 MySQLdb._exceptions.OperationalError: (2059, )
Python连接MySQL数据时:报错提示MySQLdb._exceptions.OperationalError: (2059, <NULL>). Python包: mysqlclien ...
- 4-7 3D绘图
In [1]: import matplotlib.pyplot as plt import numpy as np from mpl_toolkits.mplot3d import Axes3D % ...
- centos7 下面显卡驱动安装
一.安装驱动 屏蔽默认的nouveau cd /lib/modprobe.d/ sudo vim dist-blacklist.conf 将nvidiafb注释掉 #blacklist nvidiaf ...
- AWS之EC2实例搭建LAMP服务器
在 Amazon Linux 2 上安装 LAMP Web 服务器 创建EC2实例,在安全组添加HTTP(80)规则 步骤 1:准备 LAMP 服务器 1.使用putty连接到你的EC2实例上(AMI ...
- kali下ll命令无法使用
重装了系统之后,使用ll命令竟然发现报错了. bash: ll:未找到命令 果断解决一波: vim ~/.bashrc 将alias ll=’ls -l‘前面的注释符号#删掉 运行 问题完 ...
- jenkins slave上执行脚本报错
jenkins slave上执行脚本报错 解决方法:在系统配置中设置shell execuate C:\Windows\system32\cmd.exe 保存即可