RN需要一个JS的运行环境, 在IOS上直接使用内置的javascriptcore, 在Android 则使用webkit.org官方开源的jsc.so。 此外还集成了其他开源组件,如fresco图片组件,okhttp网络组件等。
RN 会把应用的JS代码(包括依赖的framework)编译成一个js文件(一般命名为index.android.bundle), , RN的整体框架目标就是为了解释运行这个js 脚本文件,如果是js 扩展的API, 则直接通过bridge调用native方法; 如果是UI界面, 则映射到virtual DOM这个虚拟的JS数据结构中,通过bridge 传递到native , 然后根据数据属性设置各个对应的真实native的View。
JavaScriptCore(JSC):JavaScript引擎,用于执行JS代码。
Yoga:UI引擎,用于计算元素在用户屏幕上展示的位置。
JSI:JavaScript Interface
Bridge三个特点:
异步设计的好处是不阻塞,这种设计在大部分情况下性能满足需求,但是在某些情况下就会出问题,比如瀑布流滚动。
当瀑布流向下滑动的时候,需要发请求给服务端拿数据进行下一步渲染。
滚动事件发生在UI thread,然后通过Bridge发给JS thread。JS thread 监听到消息后发请求,服务端返回数据,再通过Bridge返回给Native进行渲染。由于都是异步,就会出现空白模块,导致性能问题。
目前 RN 使用 Bridge Module 来让 JS 和 Native 线程进行通信,每次利用 Bridge 发送数据时,都需要转换为 JSON, 而收到数据时也需要进行解码。
这就意味着 JavaScript 和 Native 直接是隔离的,也就是 JS 线程不能直接调用 Native 线程上的方法。
FB团队在2018年提出了新架构:
在全新架构中,Bridge 将被一个名为 JavaScript Interface 的模块所代替,它是一个轻量级的通用层,用 C++ 编写,JavaScript Engine 可以使用它直接执行或者调用 native。
通用层代表着:JSI 让 JavaScript 接口将与 Engine 分离,这意味着新架构支持 RN 直接使用其他 JavaScript 引擎,比如 Chakra、v8、Hermes 等等。。
当前的架构使用的是JavaScriptCore引擎,bridge只兼容该引擎。而JSI并非如此,它将JavaScript接口与引擎解耦,这意味着新架构可以使用其他JavaScript引擎,如Chakra,v8,Hermes等,因此它是“通用”的。
主要有JSI、Fabric、TurboModules、CodeGen、LeanCode组成。
JSI是Javascript Interface的缩写,一个用C++写成的轻量级框架,它作用就是通过JSI,JS对象可以直接获得C++对象(Host Objects)引用,并调用对应方法。
Fabric 是新的渲染系统,它将取代当前的 UI Manager。
UI Manager:
当 App 运行时,React 会执行你的代码并在 JS 中创建一个 ReactElementTree ,基于这棵树渲染器会在 C++ 中创建一个 ReactShadowTree。UI Manager 会使用 Shadow Tree 来计算 UI 元素的位置,而一旦 Layout 完成,Shadow Tree 就会被转换为由 Native Elements 组成的 HostViewTree(例如:RN 里的
会变成 Android 中的 ViewGroup 和 iOS 中的 UIView)。
而之前线程之间的通信都发生在 Bridge 上,这就意味着需要在传输和数据复制上耗费时间。通过JSON格式来传递消息,每次都要经历序列化和反序列化。
而得益于前面的 JSI, JS 可以直接调用 Native 方法,其实就包括了 UI 方法,所以 JS 和 UI 线程可以同步执行从而提高列表、跳转、手势处理等的性能。
在之前的架构中 JS 使用的所有 Native Modules(例如蓝牙、地理位置、文件存储等)都必须在应用程序打开之前进行初始化,这意味着即使用户不需要某些模块,但是它仍然必须在启动时进行初始化。
Turbo Modules 基本上是对这些旧的 Native 模块的增强,正如在前面介绍的那样,现在 JS 将能够持有这些模块的引用,所以 JS 代码可以仅在需要时才加载对应模块,这样可以将显着缩短 RN 应用的启动时间。
Fabric和Turbo Modules听起来很有前途,但是JavaScript是一门动态语言,而JSI是用C++写的,C++是一门静态语言,因此需要保证两者间的顺利通信。
这就是新架构还包括一个名为CodeGen的静态类型检查器的原因。
CodeGen使用类型确定后的JavaScript来为Turbo Modules和Fabric定义供他们使用的接口元素,并且它会在构建时生成更多的native代码,而非运行时。
众所周知,Flutter 跨平台的性能提升和解耦来自于直接使用 Skia 渲染而非系统控件,而如今 RN 也有类似的支持。
react-native-skia 需要 react-native@>=0.66 的支持,而目前它上面的操作都还是十分原始的 canvas 行为,例如通过 Circle 绘制圆形,通过 blendMode 配置重叠模式等。
可以预见目前的 react-native-skia 还有不少问题需要解决,但是它让 RN 可以更高效地使用丰富的 Canvas 能力,对于 RN 的未来而言不免是一次不错的尝试。
主线程又称 UI thread,主要负责 UI 的渲染以及用户行为的监听等等,是 App 启动时首先创建的线程
通过 JavaScript Core 或者是 Hermes 引擎,JavaScript 代码的解析和执行是由 JS 线程负责的。和浏览器环境不同的是, JS 代码解析和执行在独立 JS 线程,而不是和 UI 渲染共用一个线程。
前两个线程都比较好理解,那 Shadow 线程是做什么的呢?要回答这个问题,首先我们需要理解 React 的原理。
我们都知道在浏览器环境 React DOM 会维护一个 Virtual DOM,Virtual DOM 实际上是在内存中的一个树形结构(Tree-Like)的 JS Object,用来映射真实的 DOM Node Tree。每次 State 更新后,React 会生成一个新的 Virtual DOM,通过和旧的 virtual DOM 做对比(Diffing),从而仅仅将需要变更的部分同步到真实 DOM(Reconciliation),从而减少 Dom Tree 的变更,从而减少浏览器的绘制工作。
与之类似的是,在 React Native,React 也会维护一个类似的 “Virtual DOM”,也会有 Diffing 的过程。但是不同于浏览器环境中 React DOM 直接调用浏览器 API 来完成真正的 DOM 更新操作,Native 环境下 React Native 是通过 Bridge ,将需要变更的指令(Commands)以字符串的方式发送到 Native Side,而对应在 Native 这边负责处理这些指令的进程就是 Shadow Thread。
Shadow Thread 通过维护一个 Shadow Tree 来计算 “Virtual DOM” 在 Native 页面的实际布局,然后通过 Bridge 异步通知 Main Thread 渲染 UI。
Shadow Tree 可以理解为是 “Virtual DOM” 在 Native 的映射,拥有和 Virtual DOM 相同的树形层级关系
将 React 代码渲染到宿主平台,我们称为渲染流水线,可大致分为三个阶段:
渲染流水线存在三种不同场景:
例如要渲染该组件
function MyComponent() {
return (
<View>
<Text>Hello, World</Text>
</View>
);
}
//
1、render phase
3、mount phase
视图挂载(View Mounting):这个步骤会在对应的原生视图上执行原子变更操作,该步骤是发生在原生平台的 UI 线程的。
https://www.react-native.cn/docs/render-pipeline
根据上面的渲染得到了下面的树
我们准备把3红色的背景变为黄色:
1、render phase
3要变化,所以复制了1,2,3。没有复制 4
2、commit phase
UpdateView(**'Node 3'**, {backgroundColor: 'yellow'})
3、mount phase
视图挂载(View Mounting):同场景1
C++ 组件可以拥有状态,如果C++状态改变了怎么渲染呢,这种场景很少,我们在此不做深入了解,想了解的话看链接。
https://www.react-native.cn/docs/render-pipeline
入口文件:
import { AppRegistry } from 'react-native';
AppRegistry.registerComponent('wiseDerma', () => App);
这样,js 就在 app 端注册好了,接着就等待Native事件驱动渲染JS端定义的APP组件。
java部分:
只需要在函数上加上ReactMethod注解就可以了
js部分:
JS调用java 使用通过扩展模块require(‘NativeModules’)获取native模块,然后直接调用native公开的方法
var { NativeModules } = require('react-native');
NativeModules.initialize
JS 调用require(‘NativeModules’)实际上是获取MessageQueue里面的一个native模块列表的属性。
Native端调用Web端,这个比较简单,JavaScript作为解释性语言,最大的一个特性就是可以随时随地地通过解释器执行一段JS代码,所以可以将拼接的JavaScript代码字符串,传入JS解析器执行就可以,JS解析器在这里就是webView。
mWebView.evaluateJavascript("javascript: 方法名('参数,需要转为字符串')", new ValueCallback() {
@Override
public void onReceiveValue(String value) {
//这里的value即为对应JS方法的返回值
}
});
https://cloud.tencent.com/developer/article/1751479
https://cloud.tencent.com/developer/article/1036325
https://zhuanlan.zhihu.com/p/281238593
https://mp.weixin.qq.com/s?__biz=MzI1NTg3NzcwNQ==&mid=2247485511&idx=1&sn=a0d97607924513c9d794e8d4fe21072f&utm_source=tuicool&utm_medium=referral
https://mp.weixin.qq.com/s/6q5r9CMmIL3O5yyHRTU5FA
简书-2022 年 React Native 的全新架构更新
yoga
官方文档架构介绍
React Native 渲染原理