You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

364 lines
34 KiB
Markdown

2 years ago
# 直播加餐|七年,我的跨端实践和探索
> 小编说:
> 以下内容均来自蒋宏伟老师3月29日晚的直播相关直播回放见[bilibili极客时间频道](https://www.bilibili.com/video/BV1XL4y177Nx?spm_id_from=333.999.0.0)。
你好,我是蒋宏伟。今天我想和你分享一下我这七年的跨端实践和探索,希望我的过往经验和想法,能对你的工作有所帮助。
这七年大体上可以分为**学习、实践和探索的三个阶段**
**第一个阶段是 20152016 年。**2015 年,我转行开始做前端工程师,而且我大学学的也不是计算机相关的专业,技术底子很薄。因为刚刚入行,对很多东西都不懂,只能天天学习。幸好当时遇到了很好的 leader入职了 58 同城。58 的培养机制还是很好的,有小师傅、有小组长,还有技术大牛,而且他们都很“乐于助我”。
那个时候,我主要负责的是 H5 和 React Native 的业务开发。在业务开发过程中,遇到简单的问题自己翻翻书、查查资料就能搞定了,遇到自己搞不定的事情,就去请教同事,技术成长也很快。
**第二个阶段是 2017~2019 年的时候。**那个时候我参与了很多 58RN 基建的开发,并且在 18 年成为了 58RN 项目的负责人。但开发业务和做技术基础设施建设是不一样的,开发业务有产品提需求,有 PMO 把握进度。搞基建呢,你自己得有想法,你得会把业内方案和业务场景进行结合,你还得组织小伙伴,让小伙伴来支持你落地。
这几年,我看了大量的业内方案,也有了大量的落地实践,所以对很多技术原理层面东西有了更深的认知。
**第三个阶段是 2020 年到现在**。2020 年,我开始负责一个 9 人的前端团队。带团队和单兵作战很不一样单从技术层面讲遇到技术难题你要能扛得住定技术方向时你要对得起大家别把大家带错路了因此对前沿技术的探索也是团队leader的必修课。
![图片](https://static001.geekbang.org/resource/image/04/62/044468e1a271048407e61a4bf563cb62.jpeg?wh=1920x1080)
这七年无论是 H5、React Native 还是小程序等等,我都在围绕着跨端这个方向,学习、探索和实践。接下来,我想通过几个小故事和你聊一下我的心路历程。
## 过去:既做业务又做基建最幸福
我要讲的第一个故事是《学以致用的故事》。
我刚入职前几年解决的就是些小问题。这是可以预见的,你刚刚工作一两年,没有太多的积累,公司不可能一上来就让你负责一个很牛逼的项目,人都是慢慢积累的。
因此我们要先学习,把那些常见的小问题给解决好,等大家认可你的技术能力的时候,你才会有更好的机会。
我举个例子2015 年,我学 H5 的时候,从《移动 Web 手册》这本书上学到了一个很有用的、开发移动应用的小知识。它说的是我们用的 CSS 像素px并不是手机屏幕中物理像素。我们都知道一个物理像素就是屏幕上最小的发光点那 CSS 像素究竟和设备的物理像素有什么关系呢?
揭开这个谜团之前,我们还要了解一个非常重要的概念,叫做 DPRDevice Pixel Ratio也就是设备像素比。现在我们的手机都是视网膜屏大部分手机的设备像素比都是 2 或 3。
如果设备像素比等于 2 的话,那么 1 个 CSS 像素点的宽度,就是两个物理像素的宽度。既然都是物理像素了,理论上我们拿一个精度很高的尺子,就能把这个物理像素的宽度度量出来。或者查一下、量一下手机宽度,再查一下它宽度上有多个像素,也能大致估算出来。
**这样一个非常抽象的 CSS 像素,就和现实世界中的毫米给挂钩上了。**
```plain
单个物理像素宽度= 71.5 毫米/1170 物理像素 = 0.06 毫米iPhone12
1CSS 像素 = 3DPR * 0.06 毫米 = 0.18 毫米
```
这种把未知的抽象概念和已知具象概念挂钩的学习方案,我经常用,对提升学习效率有非常大的帮助。
但是你知道吗?你在写 React Native 的时候React Native 的宽度 width 是没有单位的。
当时我就糊涂了,你传个数字 100 给 React Native这个 100 到底是多宽?是 100 毫米?还是 100 的 CSS 像素的宽度?我没法把 React Native 中的单位,和我当时认知体系中的单位挂上钩。咱写代码总不能稀里糊涂写吧?总得这个最基本的概念给搞明白吧?
后来我就查资料,自己写代码验证,终于搞清楚了,原来 React Native 中的默认单位就是 Web 中的 CSS 像素。当时,我还了解到,在 Android 开发的眼中,他们把 CSS 像素叫做 dp。原来同一个东西在不同开发者口中还有不同的叫法我当时也是大呼神奇。
但你要知道,**你光知道 CSS 像素、dp 和毫米的关系是没有用的,你得学以致用。**
实际上UI 稿也有自己的像素单位,那个时候我们 UI 稿的宽度是 640 像素,这个 UI 像素既不是 CSS 像素、也不是物理像素,你还得把 UI 像素和 CSS 像素挂上钩。
那个时候前端很流行自适应布局UI 给的设计稿等比缩小或放大到手机、平板上。于是,我就写了一个 uipxToDp 的函数,专门用来做这种转换:
```plain
import {Dimensions} from 'react-native';
// 58 app 只有竖屏模式,所以可以只获取一次 width
const deviceWidthDp = Dimensions.get('window').width;
// UI 默认给图是 640 像素
const uiWidthPx = 640;
function uiToDp(uiElementPx) {
return uiElementPx * deviceWidthDp / uiWidthPx;
}
export default uipxToDp;
```
使用 uipxToDp你可以直接使用 UI 给的像素单位先在手机上把布局弄好,然后在不同机型上 uipxToDp 函数会自动把 UI 像素转换成对应的 dp这样就能等比例放到任意机型上了。
这就是我在刚刚入门的时候解决的一个小问题。
![图片](https://static001.geekbang.org/resource/image/af/46/af19026bab3b75380a360e5d6b9ebc46.jpeg?wh=1920x1080)
我要讲的第二个故事是《理论指导实践的故事》。
我平时除了前端技术、跨端技术,也爱了解一些最前沿的技术。当时,因为机缘巧合,参与了一个由物理学家张首晟投资的项目的白皮书翻译,后来我又读到张首晟教授在国内的一篇演讲稿。这篇演讲稿给了我很大的震撼,对我影响很大。
其中有句话是这么说的:
> 张首晟教授 :科学有两大方向,一是把各种形态的物质,分解到最基本的组成部分;二是用这些最基本的组成部分,构造出物质不同的态。
我是这么理解的,科学搞研究是先从上往下把事物分解,再从下往上构建出一个全新的物质出来。
那我们搞技术也可以沿用这个思路。后来,我看问题的时候,就喜欢先把问题拆解、拆解、再拆解,然后再想尽办法重组、重组、再重组。我给你举个例子,比如用户打开页面的时候,他眼中的页面加载就是白屏或者转圈。
但在我眼中页面加载,是先有版本请求、资源请求,把 JavaScript 静态资源请求回来然后Native 的 React Native 代码要初始化,下一步才是执行 JavaScript。首次执行 JavaScript 代码的时候,渲染的是一个空页面,这时候我们要先发送业务请求,在业务数据回来之后,真正的业务页面才渲染完成。
**用户的眼中,页面加载只有一步,我眼中页面加载是六步,如果需要的话我还可以细分成更多步骤。**
拆分加载页面的过程有什么用呢?是告诉用户,我们页面加载这么慢是因为它有六个步骤,你要耐心等等吗?
当然不是,用户不需要关心你的加载页面的过程是什么,他关心的是页面为什么这么慢,我们程序员不就是“用户第一”嘛,用户说慢,怎么办?我们程序员就得想办法解决,而不是给老板、给用户讲道理。
我和我的小伙伴前前后后用了 23 年的时间,方案也是改了又改,做了好几个优化项目,才把我们的一个耗时 2s 多的页面,降低到了 1s 以内,首屏平均耗时一共降低了 57%。并且,这个方案也是一个通用方案,任何 58RN 业务都能用。
这里我们卖个关子,理论上一个 React Native 页面的首屏渲染耗时可以控制在 100ms 以内,也就是用户点击跳转的动画还没有完成,这个页面就能渲染出来,也就是页面直出。从 1s 降低到 100ms这也是未来 2~3 年,我们要继续做的事情。
我在入职 58 的 3~5 年之间,很多基建工作都是围绕着性能优化来做的,这件事给我的启发就是干事情,**不仅要向业内去学,还得有方法论,才能做出最贴合自己业务的方案。**
这就是我第二个《理论指导实践的故事》。
![图片](https://static001.geekbang.org/resource/image/99/dc/9945457b5cc15956c1e19f3db58da1dc.jpeg?wh=1920x1080)
我要讲的第三个故事是《从技术深度到技术广度的故事》。我有个观点,就是“既做业务又做基建最幸福”,为什么呢?
因为你做业务的时候,你会用自己开发的产品、也能感受到产品中的问题,甚至你还能从用户、产品、老板那里收到类似反馈。这个时候,你是很容易发现业务中存在的问题的。
当初我在做 React Native 业务的时候,就花了很多时间来解决线上 BUG有时候就是一个截图连报错信息都没有你说解决起来多痛苦。后来在做 58RN 基建的时候,我就在考虑要不要做一个 React Native 的监控系统。
2020 年的时候,正好有一个机会,我和我的 leader 提出要搞一个 React Native 的监控系统,他答应了。后来,我们决定先做 Web 监控,再做的 React Native 监控,因为 Web 的体量更大。
但是当你做一个完整的大型项目的时候“I 型”技术深度是远远不够的你得在“I”上加一横让你的技术能力由“I”型变为“T”型。**这一横,我认为就是产品能力和架构能力。**
在做这个监控系统的时候,我学起了 PM 的那套,做了各种调研,甚至我还掌握了写产品文档、画产品原型图这些技能。
产品文档、产品原型图实际就是个大体架子,架子分几层、用什么材料、搭建步骤又是什么,这些产品不会管。产品不管归谁管?架子的施工步骤归架构师管。
这时候,你还得去充当架构师的角色。你得写技术文档,画架构图,还得把这些产品、技术实现细节给小伙伴讲清楚。
后来当我参与搭建大前端监控系统的时候,就做了大量的产品和架构的活。做这些活的目的是把大家连接在一起,让大伙知道我们为什么要做,又怎么做,接着才能干好活。
当然,代码还是要写的,这一方面我重点负责的是 React Native 、ES/Duird、Node.js 部分。我记得当初完成大前端监控系统二期的时候,我还统计过一次 commit 的数量,当时我的 commit 的数量是第一的,那时候的成就感还是满满的。
搭建大前端监控系统这件事,让我从原来专精 React Native 方向的程序员,变成了一个产品、架构、前端、后端都懂一点点的全能战士。这种全能战士的能力,让我**能站在对方角度思考问题,交流起来障碍就小,也更容易把人和人连接在一起,项目推进起来也会更有效率。**
这就是我《从技术深度到技术广度的故事》。
![图片](https://static001.geekbang.org/resource/image/4a/b5/4a697180584552387b104090cf4143b5.jpeg?wh=1920x1080)
## 现在:既要与时俱进也要接地气
当然,过去的已经过去,人不能永远躺在过去的功劳簿上,还是得与时俱进跟上时代的步伐。因此,我来给你讲讲我这半年多做的探索,以及我对 React Native 新架构落地的思考,这一部分有点长,我慢慢给你展开。
**2022 年,对于 React Native 是一个大年,因为 React Native 团队官宣新架构会在今年正式发布。**
React Native 的新架构要出来了,你得升级吧?总不能让团队小伙伴,一直用老架构开发吧?我有过好几次升级经验,但每次升级牵涉面都很广,非常痛苦。
升级成本高的原因,是因为有些 React Native 业务,是同时运行在几个 App 上。只升级一个 App 还不行,需要把所有关联的 App 都同时升级,不然这些 React Native 业务要同时维护多个版本,成本也是很高的。
但 2022 年新架构出来后,即便成本很高,我也想把它升上去。因为我经过调研发现,新架构的潜在收益非常大,我认为新架构的收益是能够覆盖我们升级所付出的成本的,因此等今年新架构出来后,我会继续推进升级。
熟悉 React Native 的朋友肯定知道2018 年之前 React Native 的核心能力是支持跨端、支持热更新,而且是背靠 JavaScript/TypeScript 生态的。
从 2018 年到现在React Native 又推出了好几个非常吸引我的功能,主要是 2020 年 React Native 支持了 React Hooks还有今年即将推出的 React Native 新架构。
现在 React Native 新架构的预览版已经出来了,我也第一时间进行了调研,主要有两个功能非常吸引我。**第一个是新架构会默认使用的 Hermes 引擎,第二个是新架构的 Fabric 渲染流水线。**
![图片](https://static001.geekbang.org/resource/image/df/10/dfe6e181a081a48d355077ba265b5c10.jpeg?wh=1920x1080)
先说说 Hermes 引擎为什么吸引我。**简单地讲,就是新架构默认集成的 Hermes 引擎性能更好。**
性能好的核心原因是,相对于 React Native 老架构采用的 JavaScript Core 引擎Hermes 引擎是专门为移动端打造的。
在 PC 时代,我们会说电脑的性能有点过剩,但移动端时代,因为手机体积有限,性能其实是吃紧的,这个问题在一些低端机上体现得更加明显。
现在虽然我们听说的 V8 引擎、JavaScriptCore 引擎都专门为移动端做了架构升级,但由于这些引擎早在 PC 时代就诞生了,考虑很多历史限制条件,也不可能为 React Native 做专门的定制。但 Hermes 没有这些历史包袱,可以专门为移动端来定制开发。
而React Native 的老架构默认用的是 JavaScriptCore。JavaScriptCore 也叫做 JSC它采用的是 JIT 的即时编译方案。什么叫 JIT 即时编译呢?
我用一段最简单的代码给你举个例子:
```plain
const a = 1;
console.log(a);
```
这里,你本地的 JavaScript 代码会先经过 Babel 的编译,把新语法编译为用户手机里 JavaScript 引擎支持的语法,然后下发到用户手机里去执行。
这里注意一下,**我们使用 JSC 这类 JIT 即时编译引擎,下发的依旧是 JavaScript 代码**。
但用户手机里的硬件只能执行由 0101 组成的机械码,硬件是不能直接执行 JavaScript 代码的那怎么办呢这时候JSC 这类 JIT 即时编译引擎,现在主流的做法是先将 JavaScript 代码编译为字节码,然后再编译为机器码。
因为 JSC 引擎执行代码的过程是编译一段执行一段,再编译一段再执行一段,这种一边编译一边执行的方式就叫做 JIT 即时编译。
但是,在初始化时, JSC 引擎还需要把整个 JavaScript 代码都编译和执行一次。那你可能就想到了,是不是有办法先提前编译成机械码,然后把由 0101 组成的机械码下发到用户手机上,这样 JavaScript 引擎就不需要编译,只执行机器码就行了,这样 App 的初始化速度不就快了吗?
这个在本地提前把 JavaScript 代码编译为 0101 机器码的思路很好,但是由于 0101 的机械码体积很大,是 JavaScript 的 10 倍以上,那网络耗时就上去了。那么,既然提前编译为 0101 机械码不可行,我们能不能退一步编译为字节码呢?这样也能节约一部分编译时间啊。
**Hermes 引擎就是这个思路,它在本地先将 JavaScript 编译为字节码,然后再下发字节码。**
Hermes 引擎下发的字节码的体积和 JSC 引擎下发的 JavaScript 代码是一样大的,但 **Hermes 引擎执行字节码的首屏性能,却是 JSC 引擎执行 JavaScript 首屏性能的 2 倍以上。**
这就是Hermes 引擎为什么这么吸引我的原因。
![图片](https://static001.geekbang.org/resource/image/3c/ed/3cc9ecb857d4099cb7812e2688dc9fed.jpeg?wh=1920x1080)
React Native 新架构第二个吸引我的原因是 Fabric 渲染器。什么是 Fabric 渲染器呢?
Fabric 渲染器中的通信层,相信你肯定很熟悉,但是通信层只是 Fabric 渲染器的一部分。很多人以为老架构升级到新架构,就是改改 JavaScript 和 C++ 之间通信的 JSBridge 就行了,**其实 JSBridge 这部分的优化在 0.62 版本,也就是 2020 年的版本中已经完成得差不多了。**
JSBridge 的作用是通过消息通知的形式,实现 JavaScript 和 C++ 之间的相互调用。消息通知形式中的消息,其实是一个字符串数组,这个字符串数组中包含了一系列的操作命令和操作参数。但字符串数组不能直接生成、也不能直接用,它有个序列化和反序列的过程,这个过程既浪费了时间、也浪费了内存。
**新架构中已经把 JSBridge 这种消息通知形式改为 JSIJavaScript Interface。**有了 JSI 后React Native 中的 JavaScript 就直接调用 C++了,就像 node.js 使用 addon 调用 C++ 、Flutter 用 FFI 调用 C++ ,以及 Java 使用 JNI 调用 C++ 一样。
但是JSI 只是 Fabric 渲染器的一部分。如果你看过 React Native 新老版本源码的话,你会发现新架构在兼容老架构的基础上,几乎把整个底层全给重构了。
不知道你有没看过我翻译的 [React Native 架构](https://reactnative.cn/docs/architecture-overview) ,我把这篇文章也放在了 React Native 中文网上,简单说有这几点:
* 它涉及上层 React 改动,包括 Fiber 节点、Concurrent 并发模型等等;
* 上层 React Native 的 JavaScript 部分的改动,包括 nativeFabricUIManager 渲染管理器、ScrollView 组件等等的改动;
* 在 C++ 层涉及 Shadow Tree、线程模型、视图拍平等的改动和优化
* 在 Java/OC 层面把绝大部分的 Native 组件都给重写了,而且为了兼容还保留了新老架构两套逻辑。
其中改动的细节我们暂且不聊我这里只想和你分享一下Fabric 渲染器三个吸引我的点。
* 第一个点我已经讲过了Fabric 渲染器和新架构的 API 共用了通信层,**在 JavaScript 和 C++ 通信这一层的性能也是提升了 3 倍左右。**
* 第二个点是\*\*更容易保持跨平台一致性,毕竟 C++ 这块代码都是公用的。\*\*原来的 Shadow Tree、布局逻辑、视图拍平都是在各个平台单独实现的现在新架构把这些用 Java/OC 代码实现的逻辑放到了 C++ 层。
* 第三个点,是**拥有优先处理紧急任务的能力。**新架构会集成 React18 的 Concurrent 并发模型,有些同学可能对 React18 Concurrent 并发模型有所了解,并发模型可以让优先级更高的渲染任务先执行,优先级低的渲染任务可以中断或批量渲染。
这里我们简单讲讲 Concurrent 并发模型。这有点类似我们日常处理工作,同一时刻我们一般只能处理一件事,要是同时来了几件事情怎么办?工作中我们会把事情按重要性和紧急程度分级, Concurrent 并发模型就是把渲染任务按重要性和紧急程度分级处理的策略。
对于并发模型的优化效果,我们很难拿出个数据指标说优化了 10%、20%,但从原理上,我相信是可以提高用户交互的体验的。
![图片](https://static001.geekbang.org/resource/image/63/58/630421a5d8faa52af0a1a7d505089b58.jpeg?wh=1920x1080)
看到新架构的 Hermes 引擎和 Fabric 渲染器性能确实好,你可能想赶紧用上,但别着急。
**React Native 新架构目前还是预览版,这也意味着新架构还没有经过大规模的应用,如果贸然用到业务中去,可能会有风险。**
我们这些搞技术的人,热爱前沿技术是一件好事,但用什么技术得根据自己业务的实际情况来。换句话说,我们既要与时俱进,也要接地气,不能飘着,这样才更容易推动技术落地。
技术要想落地至少分为三步,首先得确定收益,对业务没有收益的事情就不考虑了。其次是确定成本和风险,最关键的控制风险,新技术线上出了大规模故障我们是要担责任的。无论通过业务试点、灰度、降级,还是其他别的方案,我们要把风险控制好,新技术才能进入大规模应用阶段。
但任何技术内部的技术点都是存在生命周期的,这就像生物体内的细胞存在新陈代谢一样,新的技术点会慢慢地替代老的技术点,而老的技术点会被慢慢淘汰。
我在和用到 Hermes 的同学聊的时候,他们的反馈都很不错,告诉我线上很稳定,而且对低端机的长列表性能有提升。可以预见的是, Hermes 这个技术点,会慢慢把 JSC 淘汰掉。
而 Fabric 这个技术点,还处于技术生命周期中的预研期,目前看来收益不错,接下来就是想办法排除风险,然后大规模应用。
关于 React Native 新架构这件事情,**我的判断是 React Native 新架构在未来 1~2 年会得到大规模应用。**为什么要 1~2 年这么长的时间呢?一方面,现在新架构还是预览版,正式版可能会在今年下半年发;另外一方面,大规模应用要排除风险,排除风险也是需要一段时间的。
那这新架构普及的 1~2 年的时间差对个人的意义在哪呢?
我认为,一个技术人越是走在业内前面,你就越是能把握住技术红利,而这对于个人成长和职级晋升都是有好处的,因此这 1~2 年的时间就是你研究新架构的最好时间。
![图片](https://static001.geekbang.org/resource/image/9a/3a/9abdc4115727645eaa46a75e037a323a.jpeg?wh=1920x1080)
## 未来:前进,探索未知的新大陆
像 React Native 新架构在 1~2 年内得到大规模应用这种事情还是比较好预测的,但我还想看到更远的未来。比如:
* 未来 2~3 年像 React Native 这类新架构会是怎么样的?
* **在更遥远的未来,比如 5~10 年后手机没有性能问题了H5 会不会替代跨端技术?**
* 如果跨端技术未来都不存在了,那我死磕跨端技术是不是浪费了青春?
对待这些问题,我内心是恐惧又兴奋,恐惧的是未来的不确定性,兴奋的是我可能问到了跨端技术的本质,恐惧和兴奋同时驱使着我去寻找这些问题的答案。
我的解题思路是这样的:
* 未来是未知的,但过去是已知,我可以从历史经验中寻找答案;
* 历史上的跨端技术的终局是什么样的?又是哪些因素导致了当前的跨端格局的形成?
* 那未来这些因素还会不会继续存在?如果这些因素还会继续存在,那么跨端技术是不是会继续存在?如果不存在跨端技术又会被什么技术代替?
我们先来看历史。
我们当前所处的时代还是移动互联网时代,我们现在所聊的跨端技术,大都指的是跨 Android、iOS 两端。历史上对类似“跨端”的技术有个更通用的叫法叫做跨平台Cross Platform
**任何跨平台、跨端软件的共性都是屏蔽底层差异,抽象统一接口,减少开发者的适配成本。**而跨平台中的平台二字,通常指的是硬件平台和软件平台,软件平台又包括操作系统平台和软件程序环境。
硬件平台中最重要的是 CPU你可能知道目前 CPU 分为两个派系,一个是 ARM 精简指令集,另一个是 X86 复杂指令集,而且每个派系之间还有不同的硬件生产商,有 Intel、AMD、高通、苹果、华为等等。
操作系统是跨硬件平台的。操作系统平台是运行在硬件平台之上电脑上常用的操作系统包括Windows、macOS、Linux手机上就是 Android、iOS。操作系统中有一类特殊的程序叫驱动程序驱动程序是硬件厂商根据操作系统编写的配置文件可以说没有驱动程序计算机中的硬件就无法工作。类似的还有苹果的 macOS 操作系统,它也通过 Rosetta 屏蔽了 ARM 和 X86 的差异。
无论是操作系统提供的驱动程序适配,还是类似 Rosetta 二进制编译器,都是操作系统为跨硬件平台所做的努力。
程序执行环境是跨操作系统平台的。在操作系统之上直接创建的应用,我们叫做原生应用。原生应用只能在一个平台运行,比如 Android 应用只能运行在 Android 操作系统上。但哪个开发者不想省事啊?历史上 Java 提过“Write once, Run anywhere”还有服务端同学熟悉的 Docker 镜像,以及前端同学熟悉的 Chrome 浏览器,都是类似的思想。
Java、Docker、Chrome、跨端框架都是在操作系统之上的程序环境 ,它们会屏蔽不同操作系统之间的差异性,并向开发者提供了统一的抽象接口,让开发者可以只写一次代码,并运行在多个操作系统之上。
**历史的经验告诉我们,无论是硬件平台、操作系统平台、还是程序环境平台,没有哪个平台是一家独大、独霸整个市场的。同一个维度的平台之间,没有赢家通吃的垄断,只有少数几个寡头之间的竞争,寡头竞争的格局产生了跨平台的需求。**
从历史和行业视角来看,无论是能源、汽车、金融、软件和现在的互联网行业,都是从原子式的竞争格局走向寡头竞争的格局的。因此,我认为无论未来的商业格局怎么变化,未来的操作系统、未来的程序环境也不会一家独大。
所以跨端、跨平台的不是伪命题。只要还存在多个寡头并存的平台竞争格局,跨端的需求就会一直存在。认识到这点之后,我对跨端技术未来的担忧也就消失了。
![图片](https://static001.geekbang.org/resource/image/2c/05/2c5f88f06d3ea1ddaa86750c3e3a5f05.jpeg?wh=1920x1080)
虽然跨端方向是一个很好的技术方向但跨端的技术很多有小程序、Flutter、React Native、H5/Hybrid具体到每个技术上又有细分。一个人的精力是有限的多嚼不烂我应该选择那个方向重点研究呢
目前我自己规划的重点研究方向是 React Native SSR。为什么我最终决定选择 React Native SSR 作为重点研究方向呢?有三个原因:
**1、“Native SSR” 的市场很大。**
目前绝大多数的 Native 应用采用的还是客户端渲染 CSR 方案,没有成熟的服务端渲染 SSR 方案。
不过我们也能看到,在 Native 应用中,大厂都有自研自己的[布局动态化、逻辑动态化](https://juejin.cn/post/7046299455397560350)的方案说明这块的需求还是很强的。Native 服务端渲染的 “SSR ” 方案主要的优势在于,业务可以随时上线、可减少包体积,还有跨端优势。
比如我们58同城推出的 Flutter 模板动态化方案 [Fair](https://github.com/wuba/fair) 、字节跳动的 Android/iOS 跨端动态化方案 [Lynx](https://github.com/hxxft/lynx-native) ,还有美团的 Flutter 跨端动态化方案 [Flap](https://tech.meituan.com/2020/06/23/meituan-flutter-flap.html) Airbnb 的 [Server-driven rendering](https://medium.com/airbnb-engineering/whats-next-for-mobile-at-airbnb-5e71618576ab) 方案等。
但是各家都是自造轮子,市面上并没有出现一个得到大规模应用的 SSR 框架,说明这类动态化技术方案还处于早期探索阶段。
**2、目前看来“Native SSR” 要成还得依赖 JavaScript 生态。**
为什么这么说呢?因为目前的 Native 应用中用的大部分是 CSR 渲染。虽然也有服务端下发 DSL 渲染静态页面的解决方案但处理复杂的业务逻辑上DSL 完全不够。
DSL 是什么呢?比如 HTML、JSON 这种非图灵完备的语言就是 DSL非图灵完备就决定了它只能解决特定领域的问题不能解决普适性的问题复杂业务更是没法处理。
而且,由于苹果公司的政策,只有特定的 JavaScript 程序才能够动态更新,因此能够同时满足跨端、图灵完备和动态更新的特点的语言只有 JavaScript。
但现有的 “Native SSR” 也有采用 JavaScript 方案的呀,为什么没有大规模应用呢?因为现有的 “Native SSR” 还要解决第二个问题,也就是同构问题。
Web SSR 也是解决了同构问题之后才大规模铺开的,也就是一套代码既可以在本地跑起来,又可以在服务端跑起来。即使 SSR 方案在服务端扛不住了,还有 CSR 方案在客户端帮忙兜底,开发 SSR 的适配成本很低,这才能流行起来。
如果你要是让开发者在Android 写一套代码在iOS 写一套代码SSR 写一套代码CSR 又写另一套代码,开发者就会说,“你这方案再牛逼,我也不想用”,成本太高了。
**3、“Native SSR” 借助 React Native将有机会得到大规模应用。**
首先你可能会问,为什么 “Native SSR” 得借助 React Native 来实现呢?
在大规模应用 “Native SSR” 必须使用 JavaScript 方案和解决同构问题的双重前提下,目前我能想到的只有 React Native 了。但光有 React Native 还不够,还得有个 React Native 版的服务端渲染框架,类似于 Web 中 Next.js。
目前 Web SSR 的方案大致是,开发者先在本地实现一个 React 应用,然后把本地实现的 React 程序跑在 node.js 服务端上,要跑在服务端就得借助 Next.js 框架了。Next.js 把服务端渲染、路由方案都给配置好了,我们的 React 程序直接拿来用就行了。
然后 Next.js 框架执行 React 程序代码生成 HTMLHTML 下发到浏览器就能把 Web 页面首屏直接渲染出来了。
那同样的React Native 要大规模实现 SSR就得造一个 Next.js 的轮子。让 React Native 能同时在本地和服务端运行起来。首先开发者在本地,能通过 “类 Next.js” 的框架使用客户端渲染 CSR 的方式,开发 React Native 应用,然后在服务端能通过服务端渲染 SSR 的方式,执行 React Native 代码,输出一个序列化 Tree。这个序列化的 Tree 描述的就是 React Native 的静态布局结构。
接着服务端把序列化的 Tree 下发到 iOS/Android 应用上Native 应用对 Tree 进行反序列化后,直接通过 C++ 层的 [Fabric 渲染器](https://reactnative.cn/docs/render-pipeline) ,执行布局、提交和挂载操作,生成 Native 页面。
这就是借助 React Native 实现 “Native SSR” 的基本原理。
![图片](https://static001.geekbang.org/resource/image/41/c9/41334efa780c78a08bfa776b8a1f8cc9.jpeg?wh=1920x1080)
有些小伙伴可能会问,原来的 React Native 不是有热更新吗?热更新不是也可以实现动态化吗?
那 React Native 的热更新和 React Native SSR 有什么区别呢?
React Native 热更新的本质还是客户端渲染 CSR 方案,首屏渲染速度要比服务端渲染 SSR 方案慢上不少。根据我的性能优化经验,一个复杂的 React Native 应用,采用热更新方案渲染耗时在 2000ms 左右,如果做到极限可能在 700ms 左右。当然,一般达不到极限,因为越是极限,要满足的条件越苛刻,业务的应用范围越小。
CSR 渲染的渲染步骤分为 6 步:
1. 请求服务端获取最新的 Bundle 资源地址;
2. 通过返回的资源地址,下载 Bundle 资源,也就是 JavaScript 代码;
3. 初始化 JavaScript 引擎和 Native 模块;
4. 执行 JavaScript 代码,生成空页面;
5. 与此同时发起业务请求,请求最新的业务数据;
6. 业务数据回来后重新渲染,生成最终的页面。
SSR 渲染其实是对 CSR 渲染的步骤的重组,整体也是 6 步:
1. 并行请求 Tree和最新的 Bundle 资源地址;
2. 初始化 Native 模块,同时开启后台线程并行请求 Bundle 资源;
3. 使用 Tree 文件,通过 Fabric 渲染器渲染首屏页面。这里画个重点,此时用户已经可以看到业务页面了;
4. 然后再初始化 JavaScript 引擎,开始执行 JavaScript 代码;
5. 这一步有个专有名词叫做 Hyration。大致的意思原来通过 Tree 生成的页面是不可交互的“静态”页面,这时需要通过执行 JavaScript生成一个有交互的“动态”页面把原来的“静态”页面替换掉
6. 替换后的页面,就是可以交互“动态”页面了。
使用 SSR 配合预请求,理论上实现 100ms 以内的首屏渲染是没有问题的。比如,美团就实现了 [React Native SSR](https://ppt.infoq.cn/slide/show?cid=94&pid=3696) ,据说页面渲染最快可以达到 50ms 。
当然,要大规模应用,一方面要依赖 React Native 团队提供更友好的接口,另一方面还得有类似 Next.js 的框架,来大幅降低开发者的适配成本。
![图片](https://static001.geekbang.org/resource/image/20/28/20ca18b97ffa5116d1a497200yya3828.jpeg?wh=1920x1080)
好了,这就是我跨端的故事,希望我过往经验和观点能对你的工作有所帮助。