pfan123 / Articles

经验文章
169 stars 25 forks source link

React Native 跨平台思考 #96

Open pfan123 opened 3 years ago

pfan123 commented 3 years ago

跨平台其实是一个老生常谈的话题,技术方案也是历经变迁,但始终热点不断,究其原因有二:

——因此,周期性地就有问题:「XXX 跨端项目现在是否凉了」、「XXX 跨端项目以后发展前景如何」?

其实这些问题都有一个统一的回答:看(业务、团队等)场景,看(业务、团队等)需求。每一种原生跨端方案在一定历史阶段内,都有其存在的意义和价值。就此,我不再聚焦「React Native (营销或前景上)是否凉了」,仅从技术层面进行简单分析 React Native 究竟完了没完。

我们先来简单 recap 一下跨端技术发展之路,总结如下图:

img

早期出现了 Cordova、Ionic 等框架,它们本质上都是使用 HTML、CSS 和 JavaScript 进行跨平台原生应用的开发。该方案说到底是在 iOS 和 Androd 上运行 Web 应用,因此也存在较多问题,比如:

React Native 脱胎于 React 理念,它将数据与视图相隔离,React Native 代码中的标签映射为虚拟节点,由原生平台解析虚拟节点并渲染出原生组件。美好的愿景是:开发者使用 React 语法,同时开发原生应用和 Web 应用,其中组件渲染、动画效果、网络请求等都由原生平台来负责。

RN现在主要有3个线程

  1. JS thread。JS代码执行线程,负责逻辑层面的处理。Metro(打包工具)将React源码打包成一个单一JS文件(就是图中JSBundle)。然后传给JS引擎执行,现在ios和android统一用的是JSC。
  2. UI Thread(Main Thread/Native thread)。这个线程主要负责原生渲染(Native UI)和调用原生能力(Native Modules)比如蓝牙等。
  3. Shadow Thread。 这个线程主要是创建Shadow Tree来模拟React结构树。Shadow Tree可以类似虚拟dom。RN使用Flexbox布局,但是原生是不支持,所以Yoga就是用来将Flexbox布局转换为原生平台的布局方式。

React Native Architecture Components | LITSLINK Blog整体技术架构如下图:

img

React Native 主要由:

三层组成,最重要的 C++ 层实现了动态链接库,起到了衔接适配前端和原生平台作用,这个衔接具体指:使用 JavaScriptCore 解析 JavaScript 代码(iOS 上不允许用自己的 JS Engine,iOS 7+ 默认使用 JavaScriptCore,Android 也默认使用 JavaScriptCore),通过 MessageQueue.js 实现双向通信,实际上通信格式类似 JSON-RPC。

这样的效果显而易见,通过前端能力,实现了原生应用的跨平台,快速编译、快速发布

但是为什么有人觉得 React Native 要完了呢?


对 React Native 来说,上述数据通信过程是异步的,通信成本很高。除此之外,目前 React Native 仍有部分组件和 API 并没有实现平台统一,也在一定程度上需要开发者了解原生开发细节。正因如此,社区上也出现了著名文章《React Native at Airbnb》,文中表示 Airbnb 团队在技术选型上将会放弃 React Native。

这...React Native 要凉?


在我看来,像 airbnb 那样放弃 React Native,拥抱新的跨平台技术并不是每个团队都有实力和魄力施行的,而改造 React Native 是另外一些团队做出的选择。

比如携程的 CRN(Ctrip React Native)以及美团的 MRN。他们在 React Native 基础上,抹平了 iOS 和 Android 端组件开发差异,做了大量性能提升的工作。更重要的是,依托于 CRN,它在后续的产品 CRN-Web 也做了 Web 支持和接入。

另外更重要的是,React Native 也在成长。上文我们提到,React Native 通过数据通信架起了 Web 和原生平台的桥梁,而这个数据通信方式是异步的。React 工程经理 Sophie Alpert 将这种这样的设计获得了线程隔离的便利,具备了尽可能的灵活性,但是这也意味着 JavaScript 逻辑与原生能力永远无法处在同一个时空,无法共享一个内存空间。

基于这个问题,新的 React Native 技术架构将从三个方面进行革新。

  1. 改变线程模型(Threading Model),以往 React Native 的 UI 更新需要在三个不同的线程进行,新的方案使具有高优先级更新的线程,直接同步调用 JavaScript;同时低优先级的 UI 更新任务不会占用主线程。
  2. 引入异步渲染能力,实现不同优先级的渲染,同时简化渲染数据信息。
  3. 简化 Bridge 实现,使之更轻量可靠,使 JavaScript 和原生平台的调用更加高效。

新架构如下图:

React Native New Architecture | LITSLINK Blog

举个例子,新的架构这些改造能够使得“手势处理”——这个 React Native 老大难问题得到更好的解决,比如新的线程模型能够使手势触发的交互和 UI 渲染效率更高,减少异步通信更新 UI 成本,使视图尽快响应用户的交互。

上述重构的核心之一其实是使用基于 JavaScript Interface (JSI) 的新 Bridge 方案来取代之前的 Bridge 方案。新的 Bridge 方案由两部分组成:

其中 Fabric 运行 UIManager 直接用 C++ 生成 Shadow Tree,而不需要走一遍老架构的 React → Native → Shadow Tree → Native UI 路径。这就减少了通信成本,提升交互性能。这个过程依赖于 JSI,JSI 并不和 JavaScriptCore 绑定,因此我们可以实现引擎互换(比如使用 V8,或任何其他版本的 JavaScriptCore)。同时,JSI 可以获取 C++ Host Objects,并调用 Host Objects 上的方法,这样能够完成 JavaScript 和原生平台的直接感知,达到“所有线程之间的互相调用操作”,因此我们就不再依赖“将传递消息序列化,并进行异步通信”了。这也就消除了异步通信带来的拥塞等问题。新的方案也允许 JavaScript 代码仅在真正需要时加载每个模块,如果应用中并不需要使用 Native Modules(例如蓝牙功能),那么它就不会在程序打开时被加载,这样就可以提升应用的启动时间。


我们再来看看最炙手可热的 Flutter,Flutter 采用了 Dart 编程语言,它在技术设计上不同于 React Native 的一个显著特点是:Flutter 并非使用原生平台组件进行渲染。比如在 React Native 中,一个 <view> 组件会被最终编译为 iOS 平台的 UIView Element 以及 Android 平台的 View Element,但在 Flutter 中,Flutter 自身提供一组组件集合,这些组件集合被 Flutter 框架和引擎直接接管。如下图(出自 Cross-Platform Mobile Development Using Flutter):

Figure 3: How Flutter works

Flutter 组件依靠自身高性能的渲染引擎进行视图的渲染。具体来说,每一个组件会被渲染在 Skia 上,Skia 是一个 2D 的绘图引擎库,具有跨平台特点。Skia 唯一需要的就是原生平台提供 Canvas 接口,实现绘制

目前来看,Flutter 具备其他(主流)跨平台方案所不具备的技术优势,它是更底层,真正意义上的跨端,未来前景大好。但作为后入场者,也存在生态小、学习成本高等障碍,也正因为更底层,存在需要通过 bridge,调用原生平台能力的成本困扰。

总结

说了这么多,讲了一堆「技术原理」,那么到底 React Native 凉没凉呢?

其实这不是任何一个「专家」或背书的团队应该回答你的问题,需求不同、立场不同,它们都不会完全靠谱。当你了解个各个方案的技术原理,掌握了 React Native 发展路线(包括新版本的技术重构)等重要信息,结合自己业务和团队的需求、痛点,进行选择?

Other Resources

Cross-Platform Mobile Development Using Flutter

new-react-native-architecture

深入剖析 React Native 下一代架构重构

React Native 新架构分析

React Native 原理与新架构入门

React Native发布新一代JS引擎Hermes

深入理解JSCore

zerosrat commented 1 year ago

存在需要通过 bridge,调用原生平台能力的成本困扰 -> 不存在