结论
- Cordova/Ionic 仅适合小项目或集成简单页面到 apps
- React Native 天花板较低,对于交互和性能要求较高、需求复杂、需要长期快速迭代的项目优势不大。
- Flutter 起点比 RN 高,然而不成熟不适合商用。
主流混合开发方案
- Web UI + JSCore/V8/Other(PWA、Cordova、Ionic)
- Native UI + JSCore(React-Native、Weex)
- Native UI + AOT(Flutter)
- Native UI + WebUI + JSCore (小程序)
一开始 Hybrid 指的是使用 WebView 渲染页面、混合原生页面的开发模式,但是目前 Native UI + JSCore 等方案明显不属于此类,所以口头上经常把纯 Native 以外的都叫做混合开发。
但是 RN 官方并没有标榜为 Hybrid:
Build native mobile apps using JavaScript and React。
JSCore
JavaScriptCore is the built-in JavaScript engine for WebKit. It currently implements ECMAScript as in ECMA-262 specification.
一开始作为 Safari 的浏览器引擎,后来在 iOS 7 作为一个系统级 Framework 提供给开发者,活生生撑起了 RN 和 Weex 等跨平台方案。
当我们用小程序 IDE/其他模拟器运行 RN/小程序的时候基本都是使用的 PC 浏览器的引擎,和移动端的浏览器引擎很明显是不一样的,所以不要再说什么在模拟器上是没问题这种话了好吗
H5 页面渲染(Web UI)
- WebView 常见的渲染方式(WebKit):
HTML + CSS -> DOM Tree + CSSOM -> Render Tree -> Render Layer (Measure) -> GPU 渲染
CSS Object Model 是 CSS 样式的映射,在结构上和 DOM 相似,提供了 API 让开发者动态获取和修改 CSS 样式。
Render Tree 描述可见的 DOM 内容,并将 CSSOM 的样式信息关联到节点上。
CSS/JS 的加载和解析都会阻塞页面的渲染
JS 直接操作 DOM 会引起 reflow/repaint:
reflow (回流): 根据 Render Tree 布局(几何属性),意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树;
repaint (重绘): 意味着元素发生的改变只影响了节点的一些样式(背景色,边框颜色,文字颜色等),只需要应用新样式绘制这个元素就可以了;
reflow 回流的成本开销要高于 repaint 重绘,一个节点的回流往往回导致子节点以及同级节点的回流;React/Vue 用 Virtual DOM 算法去减少重复绘制不必要的 DOM:
1、用 JavaScript 对象结构表示 DOM 树的结构 (Virtual DOM),然后用根据 Virtual DOM 构建 DOM 树;
2、当状态变更的时候,重新构造一棵新的 Virtual DOM。对比新旧的 Virtual DOM 树差异;
3、把(2)的 Virtual DOM 树的差异应用到第一步构建的 DOM 树以触发渲染。
两个树的完全的 diff 算法是一个时间复杂度为 O(n^3) 的问题。但是在前端当中,你很少会跨越层级地移动DOM元素。所以 Virtual DOM 只会对同一个层级的元素进行对比:
- 在具体的开发中,无限列表、路由切换和手势交互方面的问题是比较多的。
web 端细粒度的手势检测已经不是问题,问题是通过手势检测带来的像素级动画会极大的降低帧率。
Cordova/H5 (Web UI + JSCore)
在内嵌的浏览器中运行界面和逻辑。
- 实现成本最低;
- 运行效率最差;
- 可能存在较多平台兼容问题且无太好解决办法。
React-Native (Native UI + JSCore)
使用 OEM Widgets 渲染出 Native 组件。但是也导致了相对于 React 能实现的布局,RN 仅支持 10%(CSS 子集)。
在 iOS 上 RN 通过addSubview()
去渲染 UI,参考文章。将平台兼容问题完全交给 Native 层处理,也就是常说的要写两套代码。
JSCore 的通信成为性能的瓶颈,在例如处理滑动回调时毫米级别的高频率的通信将导致性能问题。
一是因为 Bridge 本身的通信成本问题,这一点 RN 使用 JSContex 避免。
另一个则是因为 RN 的本身的渲染机制是做了节流的,防止 DOM 变化带来的复杂的高频次渲染。
(这种频繁通信的需求在传统的 Hybrid 方案中并不常见)架构上还存在诸如 ListView 长列表的性能问题。
更多可见 Airbnb 的文章
Flutter
Flutter 使用 dart 语言:
- 实现了通过 AOT 编译成多个平台的本地代码,避免了 JSCore 通信频次问题;
- dart 语言的生态小。
并且 Flutter 通过将 Widgets 相关内容移动到程序包中并通过 Canvas 的方式绘制,不再使用平台的 OEM Widgets:
- 在跨平台的兼容方面较优,可以实现高度自定义而不受平台限制。
- APK 会相应增大;
- 性能上,Flutter 页面和 Native 页面体验接近,甚至数据上看在一些低端机上 Flutter 会更流畅,人肉眼已经很难区别。
- Android 的 Apk 增加 8M,iOS 压缩包增加 16M。
资料:
闲鱼掘金文章 & 技术文章
Alibaba 文章及 demo
小程序
基于 Web 且 UI 层与逻辑层分离,业务逻辑都跑在 JavaScript Context 中,UI 在单独的 WebView 上绘制。
可以参考官方人员的架构设计解析。
用户在屏幕点击某个按钮,开发者的逻辑层要处理一些事情,然后再通过setData引起界面变化,整个过程需要四次通信。对于一些强交互(例如拖动视频进度条)的场景,这样的处理流程会导致用户的操作很卡。
对于这种强交互的场景,我们引入了原生组件,这样用户和原生组件的交互可以节省两次通信。
其他参考文章:
React: The Virtual DOM
Why Flutter doesn’t use OEM widgets
Performance Limitations of React Native and How to Overcome Them
React Native vs Real Native Apps