热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

解密与思考Rax小程序运行时方案

微信小程序开发栏目介绍Rax小程序运行时的方案。

微信小程序开发教程栏目介绍Rax 小程序运行时的方案。

对于现代的前端框架(React/Vue)来说,底层基本都是通过调用 DOM API 来创建视图。而小程序的视图层模板是需要开发者事先写好的,这意味着动态创建 DOM 的方式在小程序中不被允许。但是,小程序的自定义组件具有的『自引用』特性为动态创建 DOM 打开了突破口。所谓自引用,就是自定义组件支持使用自己作为子节点,也就意味着通过递归引用的方式,我们能够构造任意层级和数量的 DOM 树。

举例来说,假设一个小程序自定义组件 element 的 WXML 模板如下所示:

            {{r.content}}

注意到,element 在模板中递归引用了自身,并通过条件判断终止递归。那么,当逻辑层通过 setData 传递了以下一份数据过来时:

{
  "nodeId": "1",
  "tagName": "view",
  "children": [
    {
      "nodeId": "2",
      "tagName": "text",
      “content”: “我是?"
    },
    {
      "nodeId": "3",
      “tagName": "text",
      "content": "rax"    }
  ]
}

最终呈现出来的视图便成了:


  我是
  rax

通过这种方式,我们巧妙地实现了在 WXML 模板固定的情况下,根据传入的 setData 数据来动态渲染视图的能力。而这,也正是运行时方案能够诞生的基础。

基本原理

Rax 的运行时方案脱胎自 kbone——微信官方推出的小程序与 web 端同构解决方案。kbone 的设计原理可以参考其官网介绍,简单总结就是通过在逻辑层模拟 DOM/BOM API,将这些创建视图的方法转换为维护一棵 VDOM 树,再将其转换成对应 setData 的数据,最后通过预置好的模板递归渲染出实际视图。从 DOM API 到维护 VDOM 树的过程基本原理并不复杂,createElement/appendChild/insertBefore/removeChild 等对应着基本的数据结构的操作。

熟悉 Rax 的同学应该知道,为了支持跨端,Rax 有 driver 的设计。实际上,我们完全可以针对小程序端再编写一个 driver,根据上述原理实现其接口 API 即可。但我们最后的选择还是通过更底层的模拟 BOM/DOM API 来完成了整个渲染机制。这么做的考量是,第一,基于 kbone 开发,这是最快的一套方案,小程序端的 driver 只需复用 web 端的 driver-dom 即可,毕竟底层的 documentwindow 变量都已经模拟好;第二,则是因为我们想为开发者提供更贴近 web 的开发体验。这套方案意味着开发者除了使用 JSX 之外,也是支持直接使用 BOM/DOM API 创建视图的,灵活度会更高一点。我们把目光拉长到整个市面上的小程序运行时框架,remax 通过 react-reconciler 直接从 VDOM 层和小程序对接(类似上面说的 Rax 小程序 driver 设计),而 kbone 和 Taro 3.0 都选择通过模拟 Web 环境来实现渲染。这也与框架开发人员的设计意图有关,见仁见智。Rax 小程序运行时方案的基本原理图如下所示:

MPA or SPA ?

以上架构是逐步演进的结果。最初,我们使用了 webpack 的多 entry 模式打包运行时小程序代码,也就是每个页面都会作为一个 entry 独立打包。这使得从行为上来说小程序更像一个 MPA。这带来的问题就是页面间公共依赖的代码不在同一内存中执行,与原生小程序表现不符。这个差异导致我们最终决定变更工程打包模式。目前版本的 Rax 运行时小程序更符合 SPA 的形式,所有的业务代码都打包到了一个 JS 文件中。

我们将 Rax 工程入口的 rax-app 包在小程序运行时上的链路做了一定改造,其在初始化时会根据路由返回各个页面的 render 函数,该 render 函数创建 root 节点(document.createElement)将对应的 Rax 组件挂载至 其上,并将 root 节点 append 到 body 节点(document.body.appendChild)。小程序每个页面在 onLoad 生命周期中,会创建一个独立的 document 并设置为全局变量,然后调用其对应的 render 函数进行各个页面的独立渲染。

性能调优

从上面的小程序运行时原理来看,其性能相比原生是存在一定差距的,这主要由以下几个方面造成:第一:逻辑层运行完整的 Rax + 通过模拟 DOM/BOM API 处理 VDOM 并生成 setData 数据,需要消耗更多的计算时间;第二,相比原生小程序需要传递更多 setData 数据,如果容器层数据序列化能力较弱,会大大增加数据传输耗时;第三,视图层通过自定义组件递归动态生成视图,而我们知道递归动作本身就是一个性能损耗点。此外,由于无法预先知晓用户需要绑定的属性和事件,自定义组件模板中只能将所有属性和事件预先绑好,这导致小程序运行过程中会触发很多无用的事件,进一步加重负担。经过我们的 benchmark 计算,在支付宝小程序平台上,运行时小程序框架(包括 Rax/Taro/Remax 等)与原生小程序存在约 40% 的性能差距。

Rax 小程序运行时发布后,经测试其性能相比其他运行时框架存在着较为明显的差距,于是我们启动了性能调优的专项计划。通过以下方面的重构,成功将 Rax 小程序运行时小程序的性能拉升至业界领先水平,与 Taro/Remax 基本处于同一水平线。

  • 更新数据精确化。在旧版本中,setData 的数据是全量更新的,虽然有 dom 子树分割批量更新的设计,但是数据传输仍然存在大量冗余。重构版本中,Rax 增加了节点渲染判断,未挂载节点无须触发更新;将所有更新收拢至顶层 root 节点统一批量处理, 并且通过精确计算数据更新的 path,实现局部更新。比如某次更新节点的 class 属性时,setData 的数据可能是:

    {   "root.children.[0].children.[1].class": "active"}
  • 内置小程序组件无需维护其属性列表,而是根据用户传参直接赋值。旧版本中,我们维护了所有内置组件的属性,在获取属性值的时候均需要调用 domNode.getAttribute,具有一定性能开销。重构版本 Rax 直接根据用户传参给属性赋值,并将默认值设置的操作移至视图层 WXS/SJS 中处理。

  • 更新 miniapp-render 中的数据结构。经过梳理,Rax 移除了冗余的 tree 数据,重写了 getaElementById 等 API;重构了 attribute、classList 等类;使用了更符合场景需要的 Map/Set 等数据结构,提升了整体的数据处理性能。

  • 渲染模板优化。在支付宝小程序中,Rax 使用 template 进行递归调用;在微信中,Rax 使用 template 调用 element 再调用 template 的形式以避免微信端递归调用 template 的层数限制。在模板中,我们尽量使用 template is 语法进行判断,减少 a:if/wx:if 条件判断,提升模板递归时的性能。

混合使用

无论是出于旧有业务的迁移,或者是出于性能考虑,Rax 小程序运行时中都存在着混合使用的需求。目前,Rax 已经打通与小程序内置组件、小程序自定义组件、小程序页面、小程序插件混合使用的能力。这其中,使用小程序自定义组件是最为复杂的。

与小程序自定义组件混用

在 Rax 中使用小程序自定义组件,其引入路径需要与 usingComponents 保持一致(例如 import CustomComp from '../components/CustomComp/index')。 在编译阶段,Rax 工程使用 Babel 插件进行代码扫描,检测到 JSX 中使用的某个组件是小程序自定义组件(根据其引入路径是否存在同名 axml 文件)时,会将其使用到的属性和事件进行缓存,然后通过 webpack 插件动态生成至递归模板中。在运行时中的创建节点阶段,通过查询缓存判断节点是否为自定义组件。若是自定义组件,则其渲染数据中会插入缓存中的属性,并且绑定事件至该自定义组件实例。

与编译时组件混用(双引擎混合)

通过 Rax 小程序编译时方案产出的组件,从使用形态上来说,可以直接视为小程序自定义组件。而 Rax 工程加强了运行时与编译时的联系,当在 Rax 小程序运行时中使用编译时组件 npm 包时,用户无需引入组件的具体路径,只需像使用普通组件时一样引入,Rax 工程将自动根据该组件 package.json 中是否存在 miniappConfig 字段来判断其是否为一个 Rax 多端组件,然后直接使用其编译时的组件实现。

未来优化方向

Rax 作为业界唯一一个同时支持编译时和运行时引擎的小程序开发方案,其双引擎混合使用的能力能够较为完美地实现性能与开发效率的平衡。在未来,Rax 将实现更为灵活的双引擎混合使用方式,如支持在单个项目中指定某个组件以编译时引擎编译,为业务提供更高的灵活度。

总结

以上就是 Rax 小程序运行时方案的原理解析。运行时方案解决了编译时方案天生的语法限制问题,但是也存在着明显的性能掣肘。可以说,在当前 2020 年这个节点,小程序开发仍然没有所谓的银弹,也许 Rax 小程序双引擎的融合会是一个相对范围内的最优解。逆标准而上的小程序究竟能走多远,谁也无法下定论,未来相当一段时间内,开发者仍然要面临种种问题。站在小程序开发框架的角度,只希望所有开发者能够选择对自己最合适的框架,爽快而又高效地完成小程序的开发。

相关免费学习推荐:微信小程序开发教程

以上就是解密与思考Rax 小程序运行时方案的详细内容,更多请关注其它相关文章!


推荐阅读
author-avatar
手机用户2502940165
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有