怎么理解React Native的新架構(gòu)?
Facebook 在 2018 年 6 月官方宣布了大規(guī)模重構(gòu) React Native 的計劃及重構(gòu)路線圖。目的是為了讓 React Native 更加輕量化、更適應(yīng)混合開發(fā),接近甚至達(dá)到原生的體驗。
之前我還寫了一篇文章分析了下 Facebook 的設(shè)計想法。經(jīng)過這么久的迭代,最近新架構(gòu)終于有了很多進(jìn)展,或者說無限接近正式 release 了,很值得和大家分享分享,這篇文章會向大家更深層次介紹新架構(gòu)的現(xiàn)狀和開發(fā)流程。
下面我們會從原理上簡單介紹新架構(gòu)帶來的一些變化,下圖是新老架構(gòu)的變化對比:

相信大家也能從中發(fā)現(xiàn)一些區(qū)別,原有架構(gòu) JS 層與 Native 的通訊都過多的依賴 bridge,而且是異步通訊,導(dǎo)致一些通訊頻率較高的交互和設(shè)計就很難實現(xiàn),同時也影響了渲染性能,而新架構(gòu)正是從這點,對 bridge 這層做了大量的改造,使得 UI 和 API 調(diào)用,從原有異步方式,調(diào)整到可以同步或者異步與 Native 通訊,解決了需要頻繁通訊的瓶頸問題。
在了解新架構(gòu)前,我們還是先聊下目前的 React Native 框架的主要工作原理,這樣也方便大家了解整體架構(gòu)設(shè)計,以及為什么 Facebook 要重構(gòu)整個框架:
ReactNative 是采用前端的方式及 UI 渲染了原生的組件,他同時提供了 API 和 UI 組件,也方便開發(fā)者自己設(shè)計、擴展自己的 API,提供了 ReactContextBaseJavaModule、ViewGroupManager,其中 ReactNative 的 UI 是通過 UIManger 來管理的,其實在 Android 端就是 UIManagerModule,原理上也是一個 BaseJavaModule,和 API 共享一個 native module。
ReactNative 頁面所有的 API 和 UI 組件都是通過 ReactPackageManger 來管理的,引擎初始化 instanceManager 過程中會讀取注入的 package,并根據(jù)名稱生成對應(yīng)的 NativeModule 和 Views,這里還僅僅是 Java 層的,實際在 C++ 層會對應(yīng)生成 JNativeModule。


切換到以上架構(gòu)圖的部分來看,Native Module 的作用就是打通了前端到原生端的 API 調(diào)用,前端代碼運行在 JSC 的環(huán)境中,采用 C++ 實現(xiàn),為了打通到 native 調(diào)用,需要在運行前注入到 global 環(huán)境中,前端通過 global 對象來操作 proxy Native Module,繼而執(zhí)行了 JNativeModule。
前端代碼 render 生成 UI diff 樹后,通過 ReactNativeRenderer 來完成對原生端的 UIManager 的調(diào)用,以下是具體的 API,主要作用是通知原生端創(chuàng)建、更新 View、批量管理組件、measure 高度、寬度等。

通過上述一系列的 API 操作后,會在原生端生成 shadow tree,用來管理各個 node 的關(guān)系,這點和前端是一一對應(yīng)的,然后待整體 UI 刷新后,更新這些 UI 組件到 ReactRootView。

通過上面的分析,不難發(fā)現(xiàn)現(xiàn)在的架構(gòu)是強依賴 nativemodule,也就是大家通常說的 bridge,對于簡單的 Native API 調(diào)用來說性能還能接受,而對于 UI 來說,每次的操作都是需要通過 bridge 的,包括高度計算、更新等,且 bridge 限制了調(diào)用頻率、只允許異步操作,導(dǎo)致一些前端的更新很難及時反應(yīng)到 UI 上,特別是類似于滑動、動畫,更新頻率較高的操作,所以經(jīng)常能看到白屏或者卡頓。
舊的架構(gòu) JS 層與 Native 的通訊都太依賴 bridge,導(dǎo)致一些通訊頻率較高的交互和設(shè)計就很難實現(xiàn),同時也影響了渲染性能,這就是 Facebook 這次重構(gòu)的主要目標(biāo),在新的設(shè)計上,React Native 提出了幾個新的概念和設(shè)計:
JSI(JavaScript interface):這是本次架構(gòu)重構(gòu)的核心重點,也正是因為這層的調(diào)整,將原有重度依賴的 native bridge 架構(gòu)解耦,實現(xiàn)了自由通訊。
Fabric:依賴 JSI 的設(shè)計,并將舊架構(gòu)下的 shadow tree 層移到 C++ 層,這樣可以透過 JSI,實現(xiàn)前端組件對 UI 組件的一對一控制,擺脫了舊架構(gòu)下對于 UI 的異步、批量操作。
TuborModule:新的原生 API 架構(gòu),替換了原有的 Java module 架構(gòu),數(shù)據(jù)結(jié)構(gòu)上除了支持基礎(chǔ)類型外,開始支持 JSI 對象,讓前端和客戶端的 API 形成一對一的調(diào)用
社區(qū)化:在不斷迭代中,F(xiàn)acebook 團(tuán)隊發(fā)現(xiàn),開源社區(qū)提供的組件和 API 越來越多,而且很多組件設(shè)計和架構(gòu)上比 React Native 要好,而且官方組件因為資源問題,投入度并不夠,對于一些社區(qū)問題的反饋,響應(yīng)和解決問題也不太及時。社區(qū)化后,大量的系統(tǒng)組件會開放到社區(qū)中,交個開發(fā)者維護(hù),例如現(xiàn)在的 webview 組件。
上面這些概念其實在架構(gòu)圖上已經(jīng)體現(xiàn)了,主要用于替換原有的 bridge 設(shè)計,下面我們將重點剖析這些模塊的原理和作用。
JSI 在 0.60 后的版本就已經(jīng)開始支持,它是 Facebook 在 JS 引擎上設(shè)計的一個適配架構(gòu),允許我們向 JavaScript 運行時注冊方法的 JavaScript 接口,這些方法可通過 JavaScript 世界中的全局對象獲得,可以完全用 C++ 編寫,也可以作為一種與 iOS 上的 Objective C 代碼和 Android 中的 Java 代碼進(jìn)行通信的方式。任何當(dāng)前使用 Bridge 在 JavaScript 和原生端之間進(jìn)行通信的原生模塊都可以通過用 C++ 編寫一個簡單的層來轉(zhuǎn)換為 JSI 模塊。
標(biāo)準(zhǔn)化的 JS 引擎接口,React Native 可以替換 v8、Hermes 等引擎。
它是架起 JS 和原生 java 或者 Objc 的橋梁,類似于老的 JSBridge 架構(gòu)的作用,但是不同的是采用的是內(nèi)存共享、代理類的方式,JS 所有的運行環(huán)境都是在 JSRuntime 環(huán)境下的,為了實現(xiàn)和 native 端直接通訊,我們需要有一層 C++ 層實現(xiàn)的 JSI::HostObject,該數(shù)據(jù)結(jié)構(gòu)支持 propName, 同時支持從 JS 傳參。
原有 JS 與 Native 的數(shù)據(jù)溝通,更多的是采用 JSON 和基礎(chǔ)類型數(shù)據(jù),但有了 JSI 后,數(shù)據(jù)類型更豐富,支持 JSI Object。
所以 API 調(diào)用流程:JS->JSI->C++->JNI->JAVA,每個 API 更加獨立化,不再全部依賴 Native module,但這也帶來了另外一個問題,相比以前的設(shè)計更復(fù)雜了,設(shè)計一個 API,開發(fā)者需要封裝 JS、C++、JNI、Java 等一套接口。當(dāng)然 Facebook 早已經(jīng)想到了這個問題,所以在設(shè)計 JSI 的時候,就提供了一個 codegen 模塊,幫忙大家完成基礎(chǔ)代碼和環(huán)境的搭建,以下我們會簡單為大家介紹怎么使用 JSI。
1、Facebook 提供了一個腳手架工程,方便大家創(chuàng)建 Native Module 模塊,需提前增加 npx 命令。
npx create-react-native-library react-native-simple-jsi
前面的步驟更多的是在配置一些模塊的信息,值得注意的是在選擇模塊的開發(fā)語言時要注意,這邊是支持很多種類型的,針對原生端開發(fā)我們用 Java&OC 比較多,也可以選擇純 JS 或者 C++ 的類型,大家根據(jù)自己的實際情況來選擇,完成后需要選擇是 UI 模塊還是 API 模塊,這里我們選擇 API(Native Module)來做測試:

以上是完成后的目錄結(jié)構(gòu),大家可以看到這是個完整的 ReactNative App 工程,相應(yīng)的 API 需要開發(fā)者在對應(yīng)的 Android、iOS 目錄中開發(fā)。

下面我們看下 C++ Moulde 的模式,相比 Java 模式,多了 cpp 模塊,并在 Moudle 中以 Native lib 的方式加載 so:


2、其實到這里我們還是沒有創(chuàng)建 JSI 的模塊,刪掉刪掉 example 目錄后,運行下面命令,完成后在 Android studio 中導(dǎo)入 example/android,編譯后 app 工程,就能打包我們 cpp 目錄下的 C++ 文件到 so。
npx react-native init example
cd example
yarn add ../

3、到這里我們完成了 C++ 庫的打包,但是不是我們想要的 JSI Module,需要修改 Module 模塊,代碼如下,從代碼中我們可以看到,不再有 reactmethod 標(biāo)記,而是直接的一些 install 方法,在這個 JSI Module 創(chuàng)建的時候調(diào)用注入環(huán)境。
public class NewswiperJsiModule extends ReactContextBaseJavaModule {
public static final String _NAME_ = "NewswiperJsi";
public NewswiperJsiModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
@NonNull
public String getName() {
return _NAME_;
}
static {
try {
_// Used to load the 'native-lib' library on application startup._
System._loadLibrary_("cpp");
} catch (Exception ignored) {
}
}
private native void nativeInstall(long jsi);
public void installLib(JavaScriptContextHolder reactContext) {
if (reactContext.get() != 0) {
this.nativeInstall(
reactContext.get()
);
} else {
Log._e_("SimpleJsiModule", "JSI Runtime is not available in debug mode");
}
}
}
public class SimpleJsiModulePackage implements JSIModulePackage {
@Override
public List<JSIModuleSpec> getJSIModules(ReactApplicationContext reactApplicationContext, JavaScriptContextHolder jsContext) {
reactApplicationContext.getNativeModule(SimpleJsiModule.class).installLib(jsContext);
return Collections.emptyList();
}
}
4、后面就是我們要創(chuàng)建 JSI Object 了,用來直接和 JS 通訊,主要是通過 createFromHostFunction 來創(chuàng)建 JSI 的代理對象,并通過 global().setProperty 注入到 JS 運行環(huán)境。
void install(Runtime &jsiRuntime) {
auto multiply = Function::createFromHostFunction(jsiRuntime,
PropNameID::forAscii(jsiRuntime,
"multiply"),
2,
[](Runtime &runtime,
const Value &thisValue,
const Value *arguments,
size_t count) -> Value {
int x = arguments[0].getNumber();
int y = arguments[1].getNumber();
return Value(x * y);
});
jsiRuntime.global().setProperty(jsiRuntime, "multiply", move(multiply));
global.multiply(2,4) // 8
到這里相信大家知道了怎么通過 JSI 完成 JSIMoudle 的搭建了,這也是我們 TurboModule 和 Fabric 設(shè)計的核心底層設(shè)計。
Fabric 是新架構(gòu)的 UI 框架,和原有 UImanager 框架是類似,前面章節(jié)也說明 UIManager 框架的一些問題,特別在渲染性能上的瓶頸,似乎基于原有架構(gòu)已經(jīng)很難再有優(yōu)化,體驗上與原生端組件和動畫的渲染性能還是差距比較大的,舉個比較常見的問題,F(xiàn)latlist 快速滑動的狀態(tài)下,會存在很長的白屏?xí)r間,交互比較強的動畫、手勢很難支持,這也是此次架構(gòu)升級的重點,下面我們也從原理上簡單說明下新架構(gòu)的特點:
1、JS 層新設(shè)計了 FabricUIManager,目的是支持 Fabric render 完成組件的更新,它采用了 JSI 的設(shè)計,可以和 cpp 層溝通,對應(yīng) C++ 層 UIManagerBinding,其實每個操作和 API 調(diào)用都有對應(yīng)創(chuàng)建了不同的 JSI,從這里就徹底解除了原有的全部依賴 UIManager 單個 Native bridge 的問題,同時組件大小的 measure 也擺脫了對 Java、bridge 的依賴,直接在 C++ 層 shadow 完成,提升渲染效率。
export type Spec = {|
+createNode: (
reactTag: number,
viewName: string,
rootTag: RootTag,
props: NodeProps,
instanceHandle: InstanceHandle,
) => Node,
+cloneNode: (node: Node) => Node,
+cloneNodeWithNewChildren: (node: Node) => Node,
+cloneNodeWithNewProps: (node: Node, newProps: NodeProps) => Node,
+cloneNodeWithNewChildrenAndProps: (node: Node, newProps: NodeProps) => Node,
+createChildSet: (rootTag: RootTag) => NodeSet,
+appendChild: (parentNode: Node, child: Node) => Node,
+appendChildToSet: (childSet: NodeSet, child: Node) => void,
+completeRoot: (rootTag: RootTag, childSet: NodeSet) => void,
+measure: (node: Node, callback: MeasureOnSuccessCallback) => void,
+measureInWindow: (
node: Node,
callback: MeasureInWindowOnSuccessCallback,
) => void,
+measureLayout: (
node: Node,
relativeNode: Node,
onFail: () => void,
onSuccess: MeasureLayoutOnSuccessCallback,
) => void,
+configureNextLayoutAnimation: (
config: LayoutAnimationConfig,
callback: () => void, // check what is returned here
// This error isn't currently called anywhere, so the `error` object is really not defined
// $FlowFixMe[unclear-type]
errorCallback: (error: Object) => void,
) => void,
+sendAccessibilityEvent: (node: Node, eventType: string) => void,
|};
const FabricUIManager: ?Spec = global.nativeFabricUIManager;
module.exports = FabricUIManager;
if (methodName == "createNode") {
return jsi::Function::createFromHostFunction(
runtime,
name,
5,
[uiManager](
jsi::Runtime &runtime,
jsi::Value const &thisValue,
jsi::Value const *arguments,
size_t count) noexcept -> jsi::Value {
auto eventTarget =
eventTargetFromValue(runtime, arguments[4], arguments[0]);
if (!eventTarget) {
react_native_assert(false);
return jsi::Value::undefined();
}
return valueFromShadowNode(
runtime,
uiManager->createNode(
tagFromValue(arguments[0]),
stringFromValue(runtime, arguments[1]),
surfaceIdFromValue(runtime, arguments[2]),
RawProps(runtime, arguments[3]),
eventTarget));
});
}
2、有了 JSI 后,以前批量依賴 bridge 的 UI 操作,都可以同步的執(zhí)行到 c++ 層,而在 c++ 層,新架構(gòu)完成了一個 shadow 層的搭建,而舊架構(gòu)是在 java 層實現(xiàn),以下也重點說明下幾個重要的設(shè)計。
FabricUIManager (JS,Java) ,JS 端和原生端 UI 管理模塊。
UIManager/UIManagerBinding(C++),C++ 中用來管理 UI 的模塊,并通過 binding JNI 的方式通過 FabricUIManager(Java) 管理原生端組件
ComponentDescriptor (C++) ,原生端組件的唯一描述及組件屬性定義,并注冊在 CoreComponentsRegistry 模塊中
Platform-speci?c
Component Impl (Java,ObjC++),原生端組件 Surface,通過 FabricUIManager 來管理

3、新架構(gòu)下,開發(fā)一個原生組件,需要完成 Java 層的原生組件及 ComponentDescriptor (C++) 開發(fā),難度相較于原有的 viewManager 有所提升,但 ComponentDescriptor 本身很多是 shadow 層代碼,比較固定,F(xiàn)acebook 后續(xù)也會提供 codegen 工具,幫助大家完成這部分代碼的自動生成,簡化代碼難度。

實際上 0.64 版本已經(jīng)支持 TurboModule,在分析它的設(shè)計原理前,我們先說明下設(shè)計這個模塊的目的,從上面架構(gòu)圖來看,主要用來替換 NativeModule 的重要一環(huán):
1、NativeModule 會包含很多我們初始化過程中就需要注冊的的 API,隨著開發(fā)迭代,依賴 NativeMoude 的 API 和 package 會越來越多,解析及校驗這些 pakcages 的時間會越來越長,最終會影響 TTI 時長
2、另外 Native module 其實大部分都是提供 API 服務(wù),其實是可以采用單例子模式運行的,而不用跟隨 bridge 的關(guān)閉打開,創(chuàng)建很多次
TurboModule 的設(shè)計就是為了解決這些問題,原理上還是采用 JSI 提供的能力,方便 JS 可以直接調(diào)用到 c++ 的 host object,下面我們從代碼層簡單分析原理。

上面代碼就是目前項目里面給出的一個例子,通過實現(xiàn) TurboModule 來完 NativeModule 的開發(fā),其實代碼流程和原有的 BaseJavaModule 大致是一樣的,不同的是底層的實現(xiàn):
1、現(xiàn)有版本可以通過 ReactFeatureFlags.useTurboModules 來打開這個模塊功能
2、TurboModule 組件是通過 TurboModuleManager.java 來管理的,被注入的 modules 可以分為初始化加載的和非初始化加載的組件
3、同樣 JNI/C++ 層也有一層 TurboModuleManager 用來管理注冊 java/C++ 的 module,并通過 TurboModuleBinding C++ 層的 proxy moudle 注入到 JS 層,到這里基本就和上面說的基礎(chǔ)架構(gòu) JSI 接上軌了,JS 中可以通過代理的 __turboModuleProxy 來完成 c++ 層的 module 調(diào)用,C++ 層透過 JNI 最終完成對 java 代碼的執(zhí)行,這里 facebook 設(shè)計了兩種類型的 moudles,longLivedObject 和 非常駐的,設(shè)計思路上就和我們上面要解決的問題吻合了。
void TurboModuleBinding::install(
jsi::Runtime &runtime,
const TurboModuleProviderFunctionType &&moduleProvider) {
runtime.global().setProperty(
runtime,
"__turboModuleProxy",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "__turboModuleProxy"),
1,
_// Create a TurboModuleBinding that uses the global_
_// LongLivedObjectCollection_
[binding =
std::make_shared<TurboModuleBinding>(std::move(moduleProvider))](
jsi::Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
return binding->jsProxy(rt, thisVal, args, count);
}));
}
const NativeModules = require('../BatchedBridge/NativeModules');
import type {TurboModule} from './RCTExport';
import invariant from 'invariant';
const turboModuleProxy = global.__turboModuleProxy;
function requireModule<T: TurboModule>(name: string): ?T {
// Bridgeless mode requires TurboModules
if (!global.RN$Bridgeless) {
// Backward compatibility layer during migration.
const legacyModule = NativeModules[name];
if (legacyModule != null) {
return ((legacyModule: $FlowFixMe): T);
}
}
if (turboModuleProxy != null) {
const module: ?T = turboModuleProxy(name);
return module;
}
return null;
}
1、新架構(gòu) UI 增加了 C++ 層的 shadow、component 層,而且大部分組件都是基于 JSI,因而開發(fā) UI 組件和 API 的流程更復(fù)雜了,要求開發(fā)者具有 c++、JNI 的編程能力,為了方便開發(fā)者快速開發(fā) Facebook 也提供了 codegen 工具,幫助生成一些自動化的代碼。
具體工具參看:https://github.com/facebook/react-native/tree/main/packages/react-native-codegen
2、以下是代碼生成的大概流程,因 codegen 目前還沒有正式 release,關(guān)于如何使用的文檔幾乎沒有,但也有開發(fā)者嘗試使用生成了一些代碼,可以參考 https://github.com/karol-bisztyga/codegen-tool。
筆者也試了,暫時行不通,還是等待 Facebook 正式 release,相信使用起來會很簡單。

上面我們從 API、UI 角度重新學(xué)習(xí)了新架構(gòu),JSI、Turbormodule 已經(jīng)在最新的版本上已經(jīng)可以體驗,而且開發(fā)者社區(qū)也用 JSI 開發(fā)了大量的 API 組件,例如以下的一些比較依賴 C++ 實現(xiàn)的模塊:
https://github.com/mrousavy/react-native-vision-camera
https://github.com/mrousavy/react-native-mmkv
https://github.com/mrousavy/react-native-multithreading
https://github.com/software-mansion/react-native-reanimated
https://github.com/BabylonJS/BabylonReactNative
https://github.com/craftzdog/react-native-quick-base64
https://github.com/craftzdog/react-native-quick-md5
https://github.com/greentriangle/react-native-leveldb
https://github.com/expo/expo/tree/master/packages/expo-gl
https://github.com/ospfranco/react-native-quick-sqlite
https://github.com/ammarahm-ed/react-native-mmkv-storage
從最新的代碼結(jié)構(gòu)來看,新架構(gòu)離發(fā)布似乎已經(jīng)進(jìn)入倒計時了,作為一直潛心學(xué)習(xí)、研究 React Native 的開發(fā)者相信一定和我一樣很期待,從 Facebook 官方了解到 Facebook App 已經(jīng)采用了新的架構(gòu),預(yù)計今年應(yīng)該就能正式 release 了,這一次我們可以相信 React Native 應(yīng)該要正式進(jìn)入 1.0 版本了吧。
開發(fā)、迭代效率、收益都有很大的提升,同樣我們也在持續(xù)關(guān)注 React Native 的新架構(gòu)動態(tài),相信整體方案、性能會越來越好,也期待快速遷移到新架構(gòu)。
