2018 年,Airbnb 放弃了继续使用 React Native,个中原因主要有两方面:
实际上,跨端方案遭遇的问题远不止这些,一些时候 Write Once, Run Everywhere 只是美好愿景
一言以蔽之,触碰到能力边界之前,跨端方案里的一切都是美好的
以 React Native 为例:
Bridge 层通过消息通信将 JAVAScript 世界与 Native 世界联系起来
Shadow Tree 用来定义 UI 效果及交互功能,Native Modules 提供 Native 功能(比如蓝牙),二者之间通过 JSON 消息相互通信
P.S.图有些旧,但不影响理解原理,更新的 React Native 架构图见 React Native 架构演进
在这样的技术架构中,写的和实际执行的都是 JavaScript,调用由 Native 提供的视图渲染能力及平台特定能力,Facebook 称之为Scripting native 方案,姑且叫做容器化 Native 跨端方案:
将 Native App 改造成标准化的容器,进而允许一套代码跨多端标准容器运行,如 React Native/Weex、Flutter
(摘自移动端跨平台技术之下的变与不变)
容器能力在很大程度上决定着开发效率,在容器提供了一致的标准支持范围内,能够愉快地一人搞定多端。
然而,一旦触及能力边界,就会面临高成本的多层联合开发(Native 层、JavaScript 引擎层、特定业务领域层、业务层……),人效上并没有优势。另一方面,如何保持多端一致性,也是个极其复杂并且充满技术挑战的问题
此外,相关的配套能力也直接关系到开发效率:
总的来说,技术上最大的困境在于:
多端一致性在技术投入上几乎是无底洞,底层的平台架构差异(UI 渲染方式、事件机制、系统 API)根深蒂固,以各类跨端方案目前的成熟度仅能覆盖极其有限的一部分,留有非常大的空白需要通过扩展容器能力来填补,包括通用的基础能力(如 UI、交互),以及面向业务领域的特定能力(如多媒体、定位)
引入 JavaScript 引擎虽然获得了动态执行代码的能力,但也带来了技术上的不确定性,几乎无法跟踪解决 JavaScript 引擎内部的崩溃或异常行为
通用的基础设施大多无法直接用于跨端场景,边边角角的配套能力都需要花力气建设,而为了满足业务快速生长,总是优先建设核心关键能力,配套支持通常是滞后的,同样影响着效率
事实上,Flutter(目前看起来)同样面临这些技术困境,技术实现的变化并未彻底改变局面
据 2020 Q1 调查结果,Flutter 开发者认为最重要的 6 个问题是:
同时,认为最困难的 6 个问题是:
因此,跨端方案的调试、性能之痛仍在 Flutter 延续,多端差异以及配套能力的困境并没有改变
与单端开发模式相比,跨端方案的协作成本更高,体现在:
跨端方案下,跨团队协作成为了最主要的协作方式,需求串讲、开发、联调、问题排查等多个环节都需要跨团队沟通/协作,沟通成本不容忽视
长链路意味着技术细节散落在多层,各自只拥有一小部分知识:
表层业务逻辑
-----------------------------
特定业务领域框架
-----------------------------
通用前端框架/类库
-----------------------------
JavaScript引擎(扩展)
-----------------------------
Native Module | 特定业务领域能力
-----------------------------
Native通用框架
-----------------------------
Native View
-----------------------------
平台操作系统
由于每个团队都看不到全景,每一个原因不那么显而易见的问题就都要一层层向下排查,甚至涉及特定业务领域能力的部分又分为许多层……
另一方面,如此繁多的层次也造成了复杂度堆积,越往下层复杂度越高,因为不确定的可变输入越多,越难弄明白来龙去脉,排查问题的成本也越高
理想情况下,按漏斗模型逐层过滤,每一层只需要检查自己的输入输出,但滞后的配套能力让表层业务难以识别出问题所在的范围,于是容器团队成为了问题流转的过滤阀,上接纷繁的 JavaScript 业务,下连复杂的特定领域能力,大量的时间耗费在了弄清楚来龙去脉上,容器能力扩展被迫降速,反复排查已知问题……
业务视角下,对业务之下的层次职责划分并不十分清楚,因此很容易找错层/人,产生无效的“重定向”。而容器层同样也不具备全景视图,问题流转轨迹变得相当曲折,沟通成本充斥在各个环节中,制约着开发效率
对个体而言,面临的最大困难是跨端方案与 Web 标准存在些许差异,并且这些许差异不像 W3C 标准一样能写得清清楚楚:
Weex enables developers to use modern web development skills to build Android, IOS, and Web apps with a single codebase.
也就是说,通用的 Web 经验不完全适用,学习曲线并不十分友好,例如:
就像有 Native 背景的开发者学习TypeScript一样,初接触无师自通,熟悉的Class、Interface、静态类型用起来游刃有余……然而,熟知 TypeScript 的开发者一定知道个中细节存在着多少奇怪的地方
React Native 最初的出发点是:
希望 Native 开发也能像 Web 一样 Move fast
快速迭代(Rapid iteration cycle):Web 一天两版,产品迭代周期更短
快速反馈(Immediate testing feedback):Web 发布立即触达用户,A/B test 等实验结果立等可取,产品演进更快
快速开发(Rapid development velocity):刷新浏览器即可生效,不必等待重新编译 App
黯羽轻扬,公众号:前端向后React Native 从诞生到现在
因此,从需求角度来看,开发效率是次要的,动态化的灵活性、快速迭代助业务先赢才是其跨端的主要意义,或者说追求的是生产效率,而不仅是开发效率,更短的迭代周期,更快速的触达用户都是直接的生产效率进步
然而,在三大困境之下,开发效率实际上也严重影响着生产效率,但还不足以抵消快速迭代、动态发布的重大进步,此消彼长也算是一种平衡,一种可接受的妥协
理想情况下,容器应该是趋于标准化的,提供多端一致、丰富稳定的能力支持,之上的业务栈极少触及容器能力边界,从而使得容器层能够不断优化探索,更好地满足业务发展的需要
另一方面,跨端方案只是将多端不一致性带来的复杂度下沉到了容器层,独立于平台的语言环境(JavaScript 引擎、Dart 虚拟机等)能够保证上层业务逻辑的一致性,但容器层仍然需要在多端各自实现一套,如何保证容器能力的多端一致性,仍然是个大问题,也并不比非跨端方案下容易多少
因此,首先要解决容器能力丰富度的问题,将边界拓宽,从根源上减少问题。转而集中火力到真正的难题上,攻下最有价值的难点部分。同时通过虚拟架构等方式建立全职能的业务支撑团队,降低沟通成本:
其中,业务自研能力先要有标准的扩展方式,要求容器实现上易扩展,业务开发者不需要了解过多细节也能快速进入开发。调试能力在长链路的技术栈下至关重要,问题识别成本越低、准确率越高,效率越高,所能释放出来的资源就越多
从业务开发角度来看,更需要的可能是一层网关,请求过去响应回来,而不是一系列路由表,需要一跳一跳地跟踪。全职能的业务支撑团队组成局域网,让网关之后的流量得以快速流转,高效协作的同时提升业务开发的幸福感
作者:黯羽轻扬 来源:前端向后