作者:billgong,腾讯IEG前端开发工程师。
微信小程序,简称小程序,英文 mini program。是一种不需要下载安装即可在微信中使用的应用,用户扫描小程序码或搜索小程序即可打开,触手可及,用完即走,不用关心是否安装太多应用的问题。
小程序技术演进
内部开放微信原生能力
使用 WeixinJSBridge 预览图片
此类 API 最初是提供给腾讯内部一些业务使用,很多外部开发者发现了之后,依葫芦画瓢地使用了,逐渐成为微信中网页开发的事实标准。
JS-SDK 发布
2015 年初,微信发布了一整套网页开发工具包,称之为 JS-SDK,开放了拍摄、录音、语音识别、二维码、地图、支付、分享、卡券等几十个 API。让所有开发者都可以使用到微信的原生能力。
使用 JS-SDK 调用图片预览组件
JS-SDK 解决了移动网页使用微信能力不足的问题,通过暴露微信的接口使得 Web 开发者能够拥有更多的能力,然而在更多的能力之外,JS-SDK 的模式并没有解决使用移动网页遇到的体验不良的问题。
JS-SDK 的不足
用户在访问网页的时候,在浏览器开始显示之前都会有一个白屏的过程,在移动端,受限于设备性能和网络速度,白屏会更加明显。除了白屏,影响 Web 体验的问题还有缺少操作的反馈,主要表现在两个方面:页面切换的生硬和点击的迟滞感。
加载白屏,切换不流畅
此外一些开发者会使用 JS-SDK 做一些,比如假红包,伪造的官方活动等。并利用 JS-SDK 的分享能力变相的去裂变分享到各个群或者朋友圈,由于 JS-SDK 是根据域名来赋予 api 权限的,运营人员封了一个域名后,他们立马用别的域名又继续做坏,要知道注册一个新的域名的成本是很低的。
那么小程序是通过怎样的设计来改进 JS-SDK 的体验和管控上的不足?
小程序双线程架构
具体实现上小程序采用了类 web + 离线包的形式。开发上与 web 类似,门槛较低,开发效率较高。离线下载和页面预渲染功能增强了用户体验,提升了加载速度,解决了 JS-SDK 加载白屏的问题 1。小程序提供了云端更新离线包的功能,可动态更新页面,相对于 app 的更新和发布更为灵活。此外,小程序在离线包的基础上对切换动画进行优化,降低了切换页面导致的迟滞感,缓解了切换不流畅的问题 2。
小程序web+离线包模式
小程序在架构方面最大的特点是采用了双线程的开发模式,隔离了 JS 逻辑和 UI 渲染。小程序的渲染层和逻辑层分别由 2 个线程管理:渲染层的界面使用了 WebView 进行渲染,逻辑层采用 JsCore 线程运行 JS 脚本。
逻辑层:创建一个单独的线程去执行 JavaScript,在这个环境下执行的都是有关小程序业务逻辑的代码;
渲染层:界面渲染相关的任务全都在 WebView 线程里执行,通过逻辑层代码去控制渲染哪些界面。一个小程序存在多个界面,所以渲染层存在多个 WebView 线程;
通信:这两个线程的通信会经由微信客户端(下文中也会采用 Native 来代指微信客户端)做中转,逻辑层发送网络请求也经由 Native 转发,小程序的通信模型下图所示。
小程序双线程架构
JS 逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的 DOM API 和 BOM API,无法操作页面元素,能达到管控的目的,但也限制了开发者的权限:
- 不允许开发者把页面跳转到其他在线网页
- 不允许开发者直接访问 DOM
- 不允许开发者随意使用 window 上的某些未知的可能有危险的 API
这样的逻辑层与 UI 层的隔离,加上小程序的审核和举报机制,使得微信加强对小程序的管控,解决了问题 3。但这也使得开发者无法灵活的进行页面渲染。
小程序页面渲染
上面已经说了逻辑层无法操作 DOM 变更,那小程序是如何进行页面的渲染呢?小程序基于数据驱动的架构模式,基于 Virtual Dom(React 引入,真实 DOM 的一种 JS 描述方式)的概念,业务侧只需要改变数据即可引起界面变化。其中渲染层提供了带有数据绑定语法的 WXML,逻辑层提供了setData 等等 API,开发者需要进行界面变化时,只需要通过在逻辑层执行 setData 把变化的数据通过 Native 层传递到渲染层,小程序会进行 Dom Diff(DOM 结构对比并进行最小化变更的算法)等流程,最后把正确的结果更新在 Dom 树上。
小程序Virtual DOM渲染
完整的通信流程大致如下:
- 逻辑层调用宿主环境的 setData 方法。
- 逻辑层将待传输数据转换成字符串,并拼接到特定的 JS 脚本,最后将数据传输到渲染层。
- 渲染层接收到后,WebView JS 线程会对脚本进行编译,得到待更新数据后进入渲染队列,等待 WebView 线程空闲时进行页面渲染。
- WebView 线程开始执行渲染时,待更新数据会合并到视图层保留的原始 data 数据,并将新数据套用在 WXML 片段中得到新的虚拟节点树。经过新虚拟节点树与当前节点树的 diff 对比,将差异部分更新到 UI 视图。同时将新的节点树替换旧节点树,用于下一次重渲染。
小程序方案与 React Native 对比
那么小程序与现有的混合开发技术类型的异同点在哪?尤其是与 React Native 的区别,小程序技术架构为什么没有使用 React Native?
混合开发技术类型
现有的混合开发类型,基于 UI 渲染的分类来看,主要有两类:
- 基于 WebView UI 的基础方案。市面上主流,例如微信 JS-SDK,通过 JSBridge 完成 H5 和 Native 的双向通讯,从而赋予 H5 一定的原生能力。
- 基于 Native UI 的方案,例如 React-Native、Weex、Flutter 等。在赋予 H5 原生 API 能力的基础上,进一步通过 JSBridge 将 JS 解析成虚拟 DOM 传递到 Native,并使用原生渲染。
小程序也属于类型 1,本次我们主要以类型 2 中的 React Native 作为对比分析。
React Native 技术架构
框架
React Native 框架主要有三层:
- JS 层:该层提供了各种供开发者使用的组件以及一些工具库(事件分发等)。
- C++层:主要处理 java/OC 与 JS 的通信(JSBridge)以及执行 JavaScript(JS 脚本引擎)。
- Native 层(Object C/Java 层):主要包括 UI 渲染器、网络通信等工具库。根据不同操作系统有不同的实现。
UI 渲染
React Native 基于 react 框架(Virtual Dom)来进行 UI 渲染,具体的流程大致如下:
- 首先 JS 层通过 JSX 编写的 Virtual Dom 来构建 Component
- Native 层将其转成真实 DOM 插入到原生 App 的页面中。
- 当有变更,通过 diff 算法生成差异对象
- 最终由 Native 层将差异对象应用到原生 App 的页面元素上。
通信
React Native 基于 JSCore 实现 js 与 java/oc 交互,具体流程大致如下:
- 把 JSX 代码解析成 javaScript 代码
- 读取 JS 文件,并利用利用 JS 脚本引擎执行
- 返回一个数组,数组中会描述 OC/Java 对象,描述对象属性和所需要执行的方法,这样就能让这个对象设置属性,并且调用方法。
Reactive Native架构
React Native 优缺点
优势
- 原生渲染,性能更好,用户体验较好;
- React 生态较好,对前端开发友好;
- hybrid 技术跨平台开发,成本及难度低于原生;
- 可热更新,能够方便迭代。
劣势
- 支持的样式是 CSS 的子集,会满足不了 Web 开发者日渐增长的需求;
- 现有能力下还存在的一些不稳定问题,比如性能、Bug 等;
- 把渲染工作全都交由客户端原生渲染,会有更接近原生的体验,但实际上一些简单的界面元素使用 Web 技术渲染完全能胜任;
- React Native 之前爆出了一个开源协议问题(Facebook BSD+Patents ,大致内容是使用基于 Facebook BSD+Patents 协议的开源项目的开发者,未来要是因为专利问题与 Facebook 产生纠纷,那么 Facebook 将有权停止你使用该开源项目),这对于之后也是存在隐患的。
小程序不选择 React Native 原因
据小程序开发人员告知的原因如下:
- React Native 只支持 CSS 的子集,作为一个开放的生态,需要告知开发者哪些 CSS 属性能用,哪些不能用,这样的开发体验较差;(对应上面的劣势 1)
- React Native 本身存在一些问题,这些依赖 RN 的修复,同时这样就变成太过依赖客户端发版本去解决开发者那边的 Bug,修复周期太长。(对应上面的劣势 2)
- React Native 前阵子还搞出了一个开源协议问题,来说也是存在隐患的。(对应上面的劣势 4)
小程序与 React Native 相同点
- 都具有 hybrid 技术的优点:接近原生的体验,跨平台开发
- 使用 Web 相关技术框架来编写业务代码,React Native 为 React 框架,小程序为小程序开发框架。
- 各自实现了跨语言通讯方案完成 Native(Java/Objective-c/…)端与 JavaScript(小程序中为渲染层和逻辑层)的通讯
小程序与 React Native 不同点
小程序使用浏览器内核 WebView 来渲染界面(小部分原生组件由客户端参与渲染),界面主要由成熟的 Web 技术渲染,辅之大量的接口提供丰富的客户端原生能力,而 React Native 是客户端原生渲染。理论上 React Native 相对于 WebView 的性能更好,但小程序的类 web 开发对开发来说入门相对简单,像是一种开发效率与性能的双刃剑。
小程序开发注意事项
基于上面的架构分析,我们在开发中需要注意是:
- 避免使用操作操作 DOM 的 npm 包。由于逻辑层和渲染层隔离,逻辑层无法操作 DOM/BOM API,所以需要使用 DOM/BOM API 相关的 npm 包和库中不可使用。
- 避免频繁调用
setData
。由于setData
中的数据不仅需要通过 Native 层传递到渲染层,通过 DOM diff 算法等渲染成最终页面,所以需要尽量减少setData
的使用以避免性能问题。 - 避免
setData
传递大量的新数据。数据的传输会经历跨线程传输和脚本编译的过程,当数据量过大,会增加脚本编译的执行时间,占用 WebView JS 线程,从而影响到最终的渲染性能。