前言
React Native 是一个框架,是一个在JavaScript中使用React JavaScript库构建移动应用App的框架。
React Native 框架是在 React 框架的基础上, 底层通过对 iOS/Android 原生代码的封装调用, 结合 JavaScript 代码,实现跨平台的开发。
出现的历史背景及介绍
众所周知,React 是由 Facebook 公司开发,并且在2013年开源到了github,React 与 Vue 都是现在很火的前端开发库。之后 Facebook 觉得这套库在前端使用的效果很是不错,就想让移动端也支持,从而实现如他们的口号一样“Learn Once,Write Anywhere”---学习一次,任何平台都能写。所以 React Native 孕育而生。
在编码方面 React 不同于传统的 HTML+CSS+JavaScript 这一套开发方式,而是采用组件化的形式,让开发者在组件里面可以混写 HTML+JavaScript,即JSX代码。React 在框架底层设计了一个虚拟DOM,此虚拟DOM 与页面上的真实 DOM 相互映射。当业务逻辑修改了 React 组件中的state/props部分,React框架底层的diff算法会通过比较虚拟 DOM 与真实 DOM 的差,找出被修改部分,并更新差异部分(即通过调用diff算法计算出变动后的 JSON 映射文件,最终由 Native 层将此 JSON 文件映射渲染到原生 App 页面元素上)。
React 并不会在 state 更改的第一时间就去执行 diff 算法,并立即更新页面 DOM,而是将多次操作汇聚成一次批量操作,又大大提升了页面更新重绘的效率。
架构
设计理念
在端上的开发,有前辈总结了一个很精辟的观点:端上的开发无外乎三件事,“数据获取”,“状态管理”,“页面渲染”。而在跨端领域的竟争,我理解是“虚拟机”,“渲染引擎”,“原生交互”,“开发环境”的竟争。而在这几点上,React Native (以下简称 RN) 都有非常棒的解决方案。
React Native 在早期的架构上虚拟机使用的是 JSC (Javascript Core) 执行运算,这样它可以充分复用 JS 生态,吸引大量前端开发者参与。而且由于 JS 天生跨平台的特点,跨端移植 App 也顺理成章。在渲染引擎上 RN 没有直接使用 WebKit 或其它 Web 引擎,因为之前 Web 在构建复杂页面时带来的计算消耗,远比不上纯原生引擎的渲染。所以它直接复用了原生的渲染通道,这样就可以带来与原生近乎一致的体验。
不过这样带来的问题就是,在 JSC 到原生渲染这一层,用了非常多的 Bridge,并通过 JSON 序列化在多个线程里来回传递信息,这样的消耗在简单的交互过程中可能不明显,而在大量的交互与渲染上会有明显的卡顿,这也成为广为诟病的一点。不过在新的架构中, RN 也做出了新的方案去解决这些痛点,下面会有介绍。
核心架构
三个线程各自负责运算,渲染,Native 交互, 引入 JSI 标准,基于 JSI 协议实现各自方法,使得 JS 可以直接引用 C++ 对象,反之亦然。
Native Thread
- 运行在主线程上,iOS平台上运行Object-C/Swift代码,Android平台上运行Java/Kotlin代码
- 负责处理原生UI的渲染,事件响应和调用原生能力。
JS Thread
- 运行在JS引擎的JS线程上
- 解释和执行JS代码,负责处理业务逻辑,还包括了应该显示哪个界面,以及如何给页面加样式。
shadow thread
- 这个线程主要是创建Shadow Tree来模拟React结构树,Shadow Tree 类似虚拟Dom。React Native 使用Flexbox布局,但是原生是不支持,所以Yoga就是用来将Flexbox布局转换为原生平台的布局方式。
React Native 如何渲染
React Native 渲染器通过一系列加工处理,将 React 代码渲染到宿主平台。这一系列加工处理就是渲染流水线(pipeline),它的作用是初始化渲染和 UI 状态更新。 接下来介绍的是渲染流水线,及其在各种场景中的不同之处。
渲染流水线可大致分为三个阶段:
- 渲染(Render):在 JavaScript 中,React 执行那些产品逻辑代码创建 React 元素树(React Element Trees)。然后在 C++ 中,用 React 元素树创建 React 影子树(React Shadow Tree)。
- 提交(Commit):在 React 影子树完全创建后,渲染器会触发一次提交。这会将 React 元素树和新创建的 React 影子树的提升为“下一棵要挂载的树”。 这个过程中也包括了布局信息计算。
- 挂载(Mount):React 影子树有了布局计算结果后,它会被转化为一个宿主视图树(Host View Tree)。
例如要渲染该组件
function MyComponent() {
return (
<View>
<Text>Hello, World</Text>
</View>
);
}
// <MyComponent />
首次渲染流程
- Native 打开 React Native 页面
- JS 线程运行,Virtual DOM Tree 被创建
- JS 线程异步通知 Shadow Thread 有节点变更
- Shadow Thread 创建 Shadow Tree
- Shadow Thread 计算布局,异步通知 Main Thread 创建 Views
- Native Thread 处理 View 的创建,展示给用户。
更新触发重新渲染流程
- Virtual DOM Tree 发生变更(比如把下图中节点3的background-color 发生变化)
- JS 线程异步通知 Shadow Thread 有节点变更
- Shadow Tread 更新 Shadow Tree,计算新布局,同时异步通知 Main Thread 更新 View
- Main Thread 更新 UI
React Native 适合哪些场景
一,业务更新迭代较快的团队。
React Native 上手成本较低。对前端同学来说,React Native 和前端技术生态重合度很高,学习成本很低;对客户端同学来说,React Native 省去了大量的编译耗时,并且自带跨端光环, 一份源码可以同时编译成 Android 和 iOS 原生应用,并发布到安卓和苹果应用商店上。
所以,在一些业务迭代快的团队中,使用 React Native 跨端方案不仅能够节约开发成本,还能带来接近原生的体验和性能。比如,现在大火的直播、短视频赛道中,React Native 能够通过集成声网提供原生 SDK,快速开发出一个直播、短视频应用。
二,既要支持动态更新,又要支持复杂业务的场景。
无论大公司、小公司都钟情于应用的动态更新。因为动态更新能降低产品的试错成本。如果产品策略有调整,可以立马上线,线上有小问题也可以快速修复。但能够既满足动态更新,又能跨端,还能满足复杂业务需求的只有 JavaScript 语言。目前几个主流的跨端框架,除了 React Native 之外,还有小程序、Flutter。但小程序只能让你的应用运行在别人的 App 上,而 Flutter 使用的语言是 Dart 而非 JavaScript,并不能很好支持动态更新。换句话说,除了基于 JavaScript 的自研框架外,目前能够既支持动态更新,又支持复杂业务的主流移动跨端框架只有使用 JavaScript 开发的 React Native。