怎么理解React Native的新架构?

Facebook 在 2018 年 6 月官方宣布了大规模重构 React Native 的计划及重构路线图。目的是为了让 React Native 更加轻量化、更适应混合开发,接近甚至达到原生的体验。

之前我还写了一篇文章分析了下 Facebook 的设计想法。经过这么久的迭代,最近新架构终于有了很多进展,或者说无限接近正式 release 了,很值得和大家分享分享,这篇文章会向大家更深层次介绍新架构的现状和开发流程。

下面我们会从原理上简单介绍新架构带来的一些变化,下图是新老架构的变化对比:

怎么理解React Native的新架构?_第1张图片

相信大家也能从中发现一些区别,原有架构 JS 层与 Native 的通讯都过多的依赖 bridge,而且是异步通讯,导致一些通讯频率较高的交互和设计就很难实现,同时也影响了渲染性能,而新架构正是从这点,对 bridge 这层做了大量的改造,使得 UI 和 API 调用,从原有异步方式,调整到可以同步或者异步与 Native 通讯,解决了需要频繁通讯的瓶颈问题。

旧架构设计

在了解新架构前,我们还是先聊下目前的 React Native 框架的主要工作原理,这样也方便大家了解整体架构设计,以及为什么 Facebook 要重构整个框架:

  • ReactNative 是采用前端的方式及 UI 渲染了原生的组件,他同时提供了 API 和 UI 组件,也方便开发者自己设计、扩展自己的 API,提供了 ReactContextBaseJavaModule、ViewGroupManager,其中 ReactNative 的 UI 是通过 UIManger 来管理的,其实在 Android 端就是 UIManagerModule,原理上也是一个 BaseJavaModule,和 API 共享一个 native module。

  • ReactNative 页面所有的 API 和 UI 组件都是通过 ReactPackageManger 来管理的,引擎初始化 instanceManager 过程中会读取注入的 package,并根据名称生成对应的 NativeModule 和 Views,这里还仅仅是 Java 层的,实际在 C++ 层会对应生成 JNativeModule。

    怎么理解React Native的新架构?_第2张图片

    怎么理解React Native的新架构?_第3张图片

  • 切换到以上架构图的部分来看,Native Module 的作用就是打通了前端到原生端的 API 调用,前端代码运行在 JSC 的环境中,采用 C++ 实现,为了打通到 native 调用,需要在运行前注入到 global 环境中,前端通过 global 对象来操作 proxy Native Module,继而执行了 JNativeModule。

  • 前端代码 render 生成 UI diff 树后,通过 ReactNativeRenderer 来完成对原生端的 UIManager 的调用,以下是具体的 API,主要作用是通知原生端创建、更新 View、批量管理组件、measure 高度、宽度等。

    怎么理解React Native的新架构?_第4张图片

  • 通过上述一系列的 API 操作后,会在原生端生成 shadow tree,用来管理各个 node 的关系,这点和前端是一一对应的,然后待整体 UI 刷新后,更新这些 UI 组件到 ReactRootView。

    怎么理解React Native的新架构?_第5张图片

通过上面的分析,不难发现现在的架构是强依赖 nativemodule,也就是大家通常说的 bridge,对于简单的 Native API 调用来说性能还能接受,而对于 UI 来说,每次的操作都是需要通过 bridge 的,包括高度计算、更新等,且 bridge 限制了调用频率、只允许异步操作,导致一些前端的更新很难及时反应到 UI 上,特别是类似于滑动、动画,更新频率较高的操作,所以经常能看到白屏或者卡顿。

新架构设计

旧的架构 JS 层与 Native 的通讯都太依赖 bridge,导致一些通讯频率较高的交互和设计就很难实现,同时也影响了渲染性能,这就是 Facebook 这次重构的主要目标,在新的设计上,React Native 提出了几个新的概念和设计:

  1. JSI(JavaScript interface):这是本次架构重构的核心重点,也正是因为这层的调整,将原有重度依赖的 native bridge 架构解耦,实现了自由通讯。

  2. Fabric:依赖 JSI 的设计,并将旧架构下的 shadow tree 层移到 C++ 层,这样可以透过 JSI,实现前端组件对 UI 组件的一对一控制,摆脱了旧架构下对于 UI 的异步、批量操作。

  3. TuborModule:新的原生 API 架构,替换了原有的 Java module 架构,数据结构上除了支持基础类型外,开始支持 JSI 对象,让前端和客户端的 API 形成一对一的调用

  4. 社区化:在不断迭代中,Facebook 团队发现,开源社区提供的组件和 API 越来越多,而且很多组件设计和架构上比 React Native 要好,而且官方组件因为资源问题,投入度并不够,对于一些社区问题的反馈,响应和解决问题也不太及时。社区化后,大量的系统组件会开放到社区中,交个开发者维护,例如现在的 webview 组件。

上面这些概念其实在架构图上已经体现了,主要用于替换原有的 bridge 设计,下面我们将重点剖析这些模块的原理和作用。

 JSI

JSI 在 0.60 后的版本就已经开始支持,它是 Facebook 在 JS 引擎上设计的一个适配架构,允许我们向 JavaScript 运行时注册方法的 JavaScript 接口,这些方法可通过 JavaScript 世界中的全局对象获得,可以完全用 C++ 编写,也可以作为一种与 iOS 上的 Objective C 代码和 Android 中的 Java 代码进行通信的方式。任何当前使用 Bridge 在 JavaScript 和原生端之间进行通信的原生模块都可以通过用 C++ 编写一个简单的层来转换为 JSI 模块。

  • 标准化的 JS 引擎接口,React Native 可以替换 v8、Hermes 等引擎。

  • 它是架起 JS 和原生 java 或者 Objc 的桥梁,类似于老的 JSBridge 架构的作用,但是不同的是采用的是内存共享、代理类的方式,JS 所有的运行环境都是在 JSRuntime 环境下的,为了实现和 native 端直接通讯,我们需要有一层 C++ 层实现的 JSI::HostObject,该数据结构支持 propName, 同时支持从 JS 传参。

  • 原有 JS 与 Native 的数据沟通,更多的是采用 JSON 和基础类型数据,但有了 JSI 后,数据类型更丰富,支持 JSI Object。

所以 API 调用流程:JS->JSI->C++->JNI->JAVA,每个 API 更加独立化,不再全部依赖 Native module,但这也带来了另外一个问题,相比以前的设计更复杂了,设计一个 API,开发者需要封装 JS、C++、JNI、Java 等一套接口。当然 Facebook 早已经想到了这个问题,所以在设计 JSI 的时候,就提供了一个 codegen 模块,帮忙大家完成基础代码和环境的搭建,以下我们会简单为大家介绍怎么使用 JSI。

1、Facebook 提供了一个脚手架工程,方便大家创建 Native Module 模块,需提前增加 npx 命令。

npx create-react-native-library react-native-simple-jsi

前面的步骤更多的是在配置一些模块的信息,值得注意的是在选择模块的开发语言时要注意,这边是支持很多种类型的,针对原生端开发我们用 Java&OC 比较多,也可以选择纯 JS 或者 C++ 的类型,大家根据自己的实际情况来选择,完成后需要选择是 UI 模块还是 API 模块,这里我们选择 API(Native Module)来做测试:

怎么理解React Native的新架构?_第6张图片

以上是完成后的目录结构,大家可以看到这是个完整的 ReactNative App 工程,相应的 API 需要开发者在对应的 Android、iOS 目录中开发。

怎么理解React Native的新架构?_第7张图片

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

怎么理解React Native的新架构?_第8张图片

怎么理解React Native的新架构?_第9张图片

2、其实到这里我们还是没有创建 JSI 的模块,删掉删掉 example 目录后,运行下面命令,完成后在 Android studio 中导入 example/android,编译后 app 工程,就能打包我们 cpp 目录下的 C++ 文件到 so。

npx react-native init example

cd example

yarn add ../

怎么理解React Native的新架构?_第10张图片

3、到这里我们完成了 C++ 库的打包,但是不是我们想要的 JSI Module,需要修改 Module 模块,代码如下,从代码中我们可以看到,不再有 reactmethod 标记,而是直接的一些 install 方法,在这个 JSI Module 创建的时候调用注入环境。

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 getJSIModules(ReactApplicationContext reactApplicationContext, JavaScriptContextHolder jsContext) {

reactApplicationContext.getNativeModule(SimpleJsiModule.class).installLib(jsContext);

return Collections.emptyList();

}

}

4、后面就是我们要创建 JSI Object 了,用来直接和 JS 通讯,主要是通过 createFromHostFunction 来创建 JSI 的代理对象,并通过 global().setProperty 注入到 JS 运行环境。

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 设计的核心底层设计。

 Fabric

Fabric 是新架构的 UI 框架,和原有 UImanager 框架是类似,前面章节也说明 UIManager 框架的一些问题,特别在渲染性能上的瓶颈,似乎基于原有架构已经很难再有优化,体验上与原生端组件和动画的渲染性能还是差距比较大的,举个比较常见的问题,Flatlist 快速滑动的状态下,会存在很长的白屏时间,交互比较强的动画、手势很难支持,这也是此次架构升级的重点,下面我们也从原理上简单说明下新架构的特点:

1、JS 层新设计了 FabricUIManager,目的是支持 Fabric render 完成组件的更新,它采用了 JSI 的设计,可以和 cpp 层沟通,对应 C++ 层 UIManagerBinding,其实每个操作和 API 调用都有对应创建了不同的 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 操作,都可以同步的执行到 c++ 层,而在 c++ 层,新架构完成了一个 shadow 层的搭建,而旧架构是在 java 层实现,以下也重点说明下几个重要的设计。

  • FabricUIManager (JS,Java) ,JS 端和原生端 UI 管理模块。

  • UIManager/UIManagerBinding(C++),C++ 中用来管理 UI 的模块,并通过 binding JNI 的方式通过 FabricUIManager(Java) 管理原生端组件

  • ComponentDescriptor (C++) ,原生端组件的唯一描述及组件属性定义,并注册在 CoreComponentsRegistry 模块中

  • Platform-specific

  • Component Impl (Java,ObjC++),原生端组件 Surface,通过 FabricUIManager 来管理

怎么理解React Native的新架构?_第11张图片

3、新架构下,开发一个原生组件,需要完成 Java 层的原生组件及 ComponentDescriptor (C++) 开发,难度相较于原有的 viewManager 有所提升,但 ComponentDescriptor 本身很多是 shadow 层代码,比较固定,Facebook 后续也会提供 codegen 工具,帮助大家完成这部分代码的自动生成,简化代码难度。

怎么理解React Native的新架构?_第12张图片

 TurboModule

实际上 0.64 版本已经支持 TurboModule,在分析它的设计原理前,我们先说明下设计这个模块的目的,从上面架构图来看,主要用来替换 NativeModule 的重要一环:

1、NativeModule 会包含很多我们初始化过程中就需要注册的的 API,随着开发迭代,依赖 NativeMoude 的 API 和 package 会越来越多,解析及校验这些 pakcages 的时间会越来越长,最终会影响 TTI 时长

2、另外 Native module 其实大部分都是提供 API 服务,其实是可以采用单例子模式运行的,而不用跟随 bridge 的关闭打开,创建很多次

TurboModule 的设计就是为了解决这些问题,原理上还是采用 JSI 提供的能力,方便 JS 可以直接调用到 c++ 的 host object,下面我们从代码层简单分析原理。

怎么理解React Native的新架构?_第13张图片

上面代码就是目前项目里面给出的一个例子,通过实现 TurboModule 来完 NativeModule 的开发,其实代码流程和原有的 BaseJavaModule 大致是一样的,不同的是底层的实现:

1、现有版本可以通过 ReactFeatureFlags.useTurboModules 来打开这个模块功能

2、TurboModule 组件是通过 TurboModuleManager.java 来管理的,被注入的 modules 可以分为初始化加载的和非初始化加载的组件

3、同样 JNI/C++ 层也有一层 TurboModuleManager 用来管理注册 java/C++ 的 module,并通过 TurboModuleBinding C++ 层的 proxy moudle 注入到 JS 层,到这里基本就和上面说的基础架构 JSI 接上轨了,JS 中可以通过代理的 __turboModuleProxy 来完成 c++ 层的 module 调用,C++ 层透过 JNI 最终完成对 java 代码的执行,这里 facebook 设计了两种类型的 moudles,longLivedObject 和 非常驻的,设计思路上就和我们上面要解决的问题吻合了。

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(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(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;

}

 CodeGen

1、新架构 UI 增加了 C++ 层的 shadow、component 层,而且大部分组件都是基于 JSI,因而开发 UI 组件和 API 的流程更复杂了,要求开发者具有 c++、JNI 的编程能力,为了方便开发者快速开发 Facebook 也提供了 codegen 工具,帮助生成一些自动化的代码。

具体工具参看:https://github.com/facebook/react-native/tree/main/packages/react-native-codegen

2、以下是代码生成的大概流程,因 codegen 目前还没有正式 release,关于如何使用的文档几乎没有,但也有开发者尝试使用生成了一些代码,可以参考 https://github.com/karol-bisztyga/codegen-tool。

笔者也试了,暂时行不通,还是等待 Facebook 正式 release,相信使用起来会很简单。

怎么理解React Native的新架构?_第14张图片

总结

上面我们从 API、UI 角度重新学习了新架构,JSI、Turbormodule 已经在最新的版本上已经可以体验,而且开发者社区也用 JSI 开发了大量的 API 组件,例如以下的一些比较依赖 C++ 实现的模块:

  • 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

从最新的代码结构来看,新架构离发布似乎已经进入倒计时了,作为一直潜心学习、研究 React Native 的开发者相信一定和我一样很期待,从 Facebook 官方了解到 Facebook App 已经采用了新的架构,预计今年应该就能正式 release 了,这一次我们可以相信 React Native 应该要正式进入 1.0 版本了吧。

开发、迭代效率、收益都有很大的提升,同样我们也在持续关注 React Native 的新架构动态,相信整体方案、性能会越来越好,也期待快速迁移到新架构。


你可能感兴趣的:(c++,vue,编程语言,python,人工智能)