React Native 入门篇

699 阅读6分钟

前言

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 也做出了新的方案去解决这些痛点,下面会有介绍。

核心架构

image.png

三个线程各自负责运算,渲染,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 />

首次渲染流程

  1. Native 打开 React Native 页面
  2. JS 线程运行,Virtual DOM Tree 被创建
  3. JS 线程异步通知 Shadow Thread 有节点变更
  4. Shadow Thread 创建 Shadow Tree
  5. Shadow Thread 计算布局,异步通知 Main Thread 创建 Views
  6. Native Thread 处理 View 的创建,展示给用户。

image.png

更新触发重新渲染流程

  1. Virtual DOM Tree 发生变更(比如把下图中节点3的background-color 发生变化)
  2. JS 线程异步通知 Shadow Thread 有节点变更
  3. Shadow Tread 更新 Shadow Tree,计算新布局,同时异步通知 Main Thread 更新 View
  4. Main Thread 更新 UI

image.png

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。

参考文章

渲染流水线

线程模型

React Native JSI