目前在小程序社区当中存在两种小程序上层框架技术方向:
以 Mpx 为代表的重编译框架,源码采用小程序平台的dsl,编译后直接产出满足小程序规范的代码
以 [email protected] 为代表的重运行时框架,源码可以使用 Vue、React 作为上层dsl,在运行时当中注入框架代码,同时 polyfill 掉小程序在逻辑线程当中并不能访问的 Dom、Bom 等相关 api
重运行时框架已具备现代Web框架开发的效率和体验,主要解决Web标准与上层框架和小程序平台规范的差异性和兼容性问题。 重编译框架遵循小程序平台的开发规范,核心在于增强小程序能力,提高开发效率和体验,尽量与现代Web框架能力对齐。
方案设计
在整体的技术方案设计上还是按照小程序能力增强的思路进行,以最小的组件粒度来按需开启运行时渲染。
在具体实现上分为运行时和编译两部分内容。
在编译阶段主要完成:
Render Function 增强
基础模板按需生成
胶水代码注入
在运行时阶段主要完成:
propertiesToComputed
vdom tree 生成&视图渲染
组件实例上下维护
事件代理&分发
核心模块设计
render 函数增强
global.currentInject = {
moduleId: "m3f28ff60",
render: function () {
this._c("message", this.message);
({ tap: [["onViewTapBubble", 'b', "__mpx_event__"]] });
this._i(this._c("navigatorList", this.navigatorList), function (item, index) {
({ tap: [["jumpTo", item]] });
item.name;
});
this._r();
}
}
在运行时增强方案中,Mpx SFC 文件经过编译后,对于 template 处理最大的不同在于经过编译后是处理为一个执行后会生成 vdom tree 的 render 函数。
在组件初次渲染过程中,执行这个 render 函数,不仅完成了响应式数据的收集,还生成了 vdom tree 。这个 vdom tree 描述了 template 的内容,并将它注入到运行时的 render 函数中。因此,在页面的初次渲染阶段,setData 设置的数据是整个 vdom tree ,它描述了整个模板视图的内容。然后通过自定义容器组件 mpx-custom-element 的递归渲染来完成整个组件的渲染。
global.currentInject = {
moduleId: 'm3f28ff60',
render: function () {
var vnode = this.__c('block', [
this.__c(
'view',
{
class: this.__sc('root', undefined),
mpxbindevents: {...},
eventconfigs: {...}
},
[
this._i(
this._c('navigatorList', this.navigatorList),
function (item, index) {
return this.__c(
'view',
{...},
[...]
)
}
)
]
)
])
this._r(vnode)
}
}
在组件初次渲染时,通过 setData 传递到渲染层的数据是全量的 vdom tree。当组件响应用户操作或接口请求导致组件实例上的响应式数据发生变化时,会触发 Render Function 的重执行,以生成一个新的 vdom tree。考虑到逻辑层与渲染层通信的成本和性能,这涉及到前后两次 vdom tree 的 diff。最终,生成具体的 vdom 路径数据更新内容,并通过 setData 传递到渲染层,完成视图的局部更新。
mpx-custom-element 容器组件
作为基础容器组件承载模板渲染:mpx-custom-element.mpx 主要作为一个渲染容器,接收组件传递的需动态渲染的 composed tree 数据,并在其上下文中完成真实组件的渲染。需要注意的是,mpx-custom-element.mpx 本身也是一个基于模板动态渲染的组件,其最终编译输出的内容包含 wxml(视图层)/wxss(样式)/js(逻辑层)/json(配置)。 跳脱微信小程序模板递归渲染的限制:由于微信小程序的 template 能力不支持模板的自引用递归渲染,因此需要借助一层自定义组件容器来绕开这一限制,从而提供新的渲染上下文。
基础模板
所有模板动态化组件的视图都是基于 mpx-custom-element.mpx 提供的基础模板 mpx-custom-element.wxml 进行动态渲染的。在组件的编译阶段,会按需收集使用到的组件和属性,并将其输出到基础模板中,以确保基础模板的包体积最小化。
<template is="tmpl_0_container" wx:if="{{r && r.nodeType}}" data="{{ i: r }}"></template>
<template name="mpx_tmpl">
<element r="{{r}}" wx:if="{{r}}"></element>
</template>
<template name="tmpl_0_block">
<block wx:for="{{i.children}}" wx:key="index">
<template is="tmpl_1_container" data="{{i:item}}" />
</block>
</template>
<template name="tmpl_0_view">
<view class="{{i.data.class}}" bind:tap="__invoke" data-eventconfigs="{{i.data.dataEventconfigs}}" style="{{i.data.style}}" data-mpxuid="{{i.data.uid}}">
<block wx:for="{{i.children}}" wx:key="index">
<template is="tmpl_1_container" data="{{i:item}}" />
</block>
</view>
</template>
...
事件系统
事件是视图层到逻辑层的通讯方式 事件可以将用户的行为反馈到逻辑层进行处理 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数
由小程序的视图层接收事件的触发 视图层与逻辑层通信,将用户行为反馈到逻辑层处理 运行时代码模拟整个事件系统生命周期
<view
bindtap="__invoke"
bindtouchstart="__invoke"
bindtouchmove="__invoke"
bindtouchend="__invoke"
bindtouchcancel="__invoke"
bindlongpress="__invoke"></view>
可以看到在基础模板上,我们只对普通的事件如 tap、touchstart 等进行了绑定(在模板上没有绑定其他特殊的事件类型),同时每个事件使用__invoke 代理方法统一响应微信小程序触发的事件。在其他运行时方案(以 Kbone 为例)中,运行时代码实现了一整套事件系统,在__invoke 代理方法内部根据实际的事件类型触发相应的事件,并根据 vdom tree 递归寻找事件触发的上层节点,从而模拟出事件冒泡的流程,事件捕获的流程同理。
性能相关
页面初次渲染:对于 mpx 项目,最终页面的模板存在于视图层,渲染只需将模板中使用到的数据传递到视图层。而在运行时方案中,逻辑层需要将全量的 vdom tree数据传递到渲染层驱动基础模板的渲染,因此整体的数据量会大得多。 页面二次更新渲染:对于 mpx 项目,基于 Render Function 的数据 diff 最终只会收集发生变化的 key 以及 value。然而,对于运行时方案,例如使用 Vue 的 Taro 项目,如果响应式数据发生变化,重新触发 Render Function 执行及后续的 diff 操作,需要准确找到数据发生变更的 vdom 节点信息,因此传输的数据量相较于编译型框架而言也会更多。
首先,在运行时方案的整体策略上,mpx 运行时的能力是按需的,做到局部开启组件的运行时特性。因此,对于一个页面而言,可以部分走原有的编译方案,部分走运行时的方案。 其次,相较于纯运行时方案,它们的基础模板基本要做到全量的输出,而在 mpx 局部方案中,我们是按需完成基础模板的收集和输出,同时对于模板节点的属性、事件绑定也能做到按需。
小程序基于运行时的能力拓展
对于运行时方案来说,它提供了一种新的逻辑层控制视图层渲染的方式。从某种程度上说,小程序的运行时渲染可以看作是对原生小程序能力的增强,这为小程序的能力拓展带来了更多的可能性。
Zero-Bundle-Size Components(在 server 端运行的组件及其纯 js 依赖最终并不会被下载到浏览器当中运行)
loading performance(Server Components 可以直接访问后端的服务or database,对于一些场景可以减少网络请求数量,同时把一些纯计算的工作可以放到 server)
视图渲染
在小程序场景下,视图层渲染有两种方案:一种是原生小程序的渲染流程,另一种是运行时渲染流程。以 mpx 运行时能力增强为例,编译环节将 template 转换为可执行的 Render Function,运行后生成描述视图的 vdom tree,然后驱动视图层的基础模板进行渲染。在这个过程中,Render Function 的执行实际上是由 props/data 的变化驱动的,与平台和环境(例如不依赖小程序提供的API)无关。因此,Render Function 也可以在 server 端执行,生成描述视图的 vdom tree。
样式规则
组件逻辑
对于 x.server 组件而言:由 props 驱动(props 来源可以是 client 的请求,也可能是服务端访问其他服务或 database),不涉及组件自身状态的维护、生命周期、用户事件交互,只做纯渲染工作; 对于 x.client 组件而言:在浏览器中执行的代码与普通组件无异,可以进行任何操作。
server components 和 client components 混合渲染
在 RFC 中也提到了 server components 和 client components 相互引用的场景。其中,server components 最终在服务器端运行并生成 vdom tree,而 client components 则仅通过一个组件标识下发,其实例的创建在浏览器端执行。
结语
Mpx 作为一款兼具优秀开发体验和深度性能优化的增强型跨端小程序框架,我们一直致力于在此领域持续前进,同时也诚邀大家加入 Mpx 用户群,共同参与和交流。
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...