Taro 是一个开放式跨端跨框架解决方案,支持使用 React/Vue/Nerv 等框架来开发微信/京东/百度/支付宝/字节跳动/ QQ 小程序/H5 等应用。
本篇文章重点关注基于Taro Next(React)的微信小程序渲染,主要将从以下几个方面进行:
- 浏览器渲染流程
- React 渲染流程
- 微信小程序渲染流程
- 基于Taro Next(React)的微信小程序渲染流程
- 基于Taro Next(React)的微信小程序渲染问题和解决方案
浏览器渲染流程
用户通过在地址栏输入一个网站的URL后,内容显示到浏览器的完整过程
- 浏览器查看是否需要redirect, 如果需要,则redirect到对应url
- 查看是否命中缓存,如果有直接到7,如果没有继续3
- 解析域名,进行DNS查找
- 创建http(s)连接
- 开始发送请求
- 接收服务端响应
- 解析处理HTML标记并构造DOM树
- 预加载扫描仪将解析可用的内容并请求高优先级资源,如CSS、JavaScript和web字体
- 解析处理CSS并构建CSSOM树
- 下载其他资源。当CSS被解析并创建CSSOM时,其他资源,包括JavaScript文件正在下载。JavaScript被解释、编译、解析和执行。
- CSSOM树和DOM树组合 创建一个Render树。Render树保存所有具有内容和计算样式的可见节点
- 在Render树上运行布局以计算每个节点的几何体
- 将各个节点绘制到屏幕上,在绘制或光栅化阶段,浏览器将在布局阶段计算的每个框转换为屏幕上的实际像素
- 合成文档中不同的层,以确保相互重叠层以正确的顺序绘制到屏幕上,并正确显示内容
React 渲染流程
JSX 会被编译转换成 React.createElement 函数的调用,其返回值就是 VNode(JS对象),虚拟DOM节点的描述对象。
- 在首次渲染时,所有组件会创建对应的VNode
- React 将 React.render 接收到的 VNode 转化虚拟 DOM 树
- 根据虚拟 DOM 树的层级关系,构建生成出 DOM 树并渲染至屏幕中
- 状态改变时,通过diff算法对比2课虚拟DOM树,得到差异(patches)
- 将差异应用到DOM树上,并将变化更新到屏幕中(进行重新渲染)
Note: Fiber 是 React 16 中新的协调引擎。它的主要目的是使 Virtual DOM 可以进行增量式渲染。了解更多.
对于首次渲染,React 的主要工作就是将 React.render 接收到的 VNode 转化 Fiber 树,并根据 Fiber 树的层级关系,构建生成出 DOM 树并渲染至屏幕中。
而对于更新渲染时,Fiber 树已经存在于内存中了,所以 React 更关心的是计算出 Fiber 树中的各个节点的差异,并将变化更新到屏幕中。
微信小程序渲染流程
MINA 是在微信中开发小程序的框架。其目标是通过尽可能简单、高效的方式让开发者可以在微信中开发具有原生 APP 体验的服务。
MINA 提供了自己的渲染层描述语言 WXML 和 WXSS,以及基于 JavaScript 的逻辑层框架,核心是一个响应的数据绑定系统。
小程序的运行环境分成渲染层和逻辑层,其中 WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层。
小程序的渲染层和逻辑层分别由2个线程管理:
- 渲染层的界面使用了WebView 进行渲染
- 逻辑层采用JsCore线程运行JS脚本
一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端(下文中也会采用Native来代指微信客户端)做中转,逻辑层发送网络请求也经由Native转发,小程序的通信模型下图所示
渲染过程:
- 在渲染层,宿主环境(微信App)会把WXML转换成对应的JS对象(可以看作与React的VNode)
- 将JS对象再次转换成真实DOM树,交由渲染层线程渲染(Weview初始化完成后的流程与浏览器渲染流程一致)
- 数据变化时(在小程序容器中,逻辑层到渲染层的更新,只能通过 setData() 来实现。),逻辑层提供最新的变化数据,生成新的JS对象与之前的JS对象进行diff算法对比
- 将最新变化的内容反映到真实的DOM树中,更新UI
基于Taro Next(React)的微信小程序渲染流程
从实现原理上,开源社区的跨端框架大致分为下面两类:
- compile time 编译时
框架约定了一套自己的 DSL ,在编译打包的过程中,利用 babel 工具通过 AST 进行转译,生成符合小程序规则的代码,如Taro v1/2 - runtime 运行时
跨端框架真正的在小程序的逻辑层中运行起 React 或者是 Vue 的运行时,然后通过适配层,实现自定义渲染器,如Taro Next
React 16版本带来了全新的 fiber 的架构,代码拆分也非常清晰,大体上可以拆分成这三大块:
- React component API
- Reconciler
- Renderer
其中渲染器Renderer负责将内容渲染到具体的平台上。最常见的 ReactDOM 就是 web 浏览器平台的自定义渲染器。即当调用操作 WEB 浏览器 web DOM的方法,如createElement、appendhild,那么就创建/更新浏览器中的 web 页面。
扩展阅读,【2万字长文】深入浅出主流的几款小程序跨端框架原理
在一个页面加载时需要经历以下步骤:
- React把页面渲染到虚拟 DOM 中
- Taro 运行时把页面的虚拟 DOM 序列化为JSON 树状数据,并使用 setData() 驱动页面渲染
- 小程序本身渲染序列化数据,渲染到小程序页面
- 数据变化时,通过setData()去更新上面小程序的 JSON 树状数据
- JSON 树状数据被更新了,小程序会触发更新数据对应的那块视图的渲染
基于Taro Next(React)的微信小程序渲染问题和解决方案
和原生小程序或编译型小程序框架相比,基于Taro Next(React)的微信小程序渲染流程中的步骤 1 和 步骤 2 是新增的。如果页面的业务逻辑代码没有性能问题的话,大多数性能瓶颈出在步骤 2 的 setData() 上。
由于初始化渲染是页面的整棵虚拟 DOM 树,数据量比较大,因此 setData() 需要传递一个比较大的数据,导致初始化页面时会一段白屏的时间。这样的情况通常发生在页面初始化渲染的 wxml 节点数比较大或用户机器性能较低时发生。
解决方案:
- 多使用stateless component
- stateful component在setData的时候减小数据量
- 正确使用hooks dependencies, 并减少 setData 函数的调用次数
- Taro 3 官方也提供了一些种方式:
- 使用 Prerender
- 跳转预加载
- 全局配置项 baseLevel
- CustomWrapper 组件
参考文档:
https://developer.mozilla.org/zh-CN/docs/Web/Performance/How_browsers_work
https://reactjs.org/docs/rendering-elements.html#gatsby-focus-wrapper
https://reactjs.org/docs/reconciliation.html
http://yunlaiwu.github.io/blog/2017/08/14/react-virtual-dom-fiber/
https://juejin.cn/post/6923073253988810765
https://zhuanlan.zhihu.com/p/103506207
https://developers.weixin.qq.com/miniprogram/dev/framework/quickstart/framework.html
https://developers.weixin.qq.com/miniprogram/dev/framework/MINA.html
https://juejin.cn/post/6881597846307635214
https://juejin.cn/book/6844733744830480397
https://taro-docs.jd.com/taro/docs/optimized
https://docs.taro.zone/blog/2021-02-08-taro-jxpp