自从2022年2月份,vue官方团队宣布vue3作为默认版本之后,时至今日,时间已经过去了整整两年,vue3也发布了两个大的minor版本,想必大家团队的vue项目已经升级到vue3,体验到了vue3开发的丝滑与流畅程度了吧!如果还没有体验到的,应该去社区学习一波vue3的正确打开方式,也许会让你受益良多。
当然我们今天讨论的不只是vue3,而是基于它的vapor mode
,这个词汇最早出现在vue文档中,其中写到:"我们也在探索一种新的受 Solid 启发的编译策略 (Vapor Mode),它不依赖于虚拟 DOM,而是更多地利用 Vue 的内置响应性系统。"根据这段描述,我们可以推断出vapor mode
,其实就是vue3的no virtual dom
的一种新的实现模式,我们首先来聊一下virtual dom
。
virtual dom
virtual dom
这个概念最早是由React团队提出来的一种模式,事实上它就是对真实DOM节点的中间抽象层,实现了代码 -> 虚拟DOM树 -> 真实DOM树。
优势
- 跨平台能力。虚拟DOM本质上就是一个JS对象,作为抽象层可以自定义编译提供给目标平台使用,包括小程序,原生应用等;
- 数据绑定能力,较少DOM操作。对于多次DOM的操作可以合并为一次,渲染时可以减少浏览器回流,提升应用的渲染性能。
劣势
在某些特殊场景下虚拟DOM不一定快,如下:
- 首次渲染大量DOM节点时,由于多了一层虚拟DOM的计算,相比于直接往innerHTML中插入节点会慢;
- 增加应用的内存消耗,需要在内存中额外维护一份组件的虚拟DOM对象;
- 增加应用打包后的bundle体积,runtime代码必须包含虚拟DOM相关的部分。
因此为了解决上述虚拟DOM的带来的一些不足之处,同时为了提升vue框架的渲染性能,来自于Solid框架的灵感,vue-vapor应运而生。
vapor mode的工作机制
vue原有的编译运行时流程如下图所示: 而vapor mode就完全没有vnode这个抽象节点对象了,因此掐断vnode后,流程就是template -> ast -> render -> dom,以官方 Playground 提供的 Demo 为例:
vue
<script setup>
import { ref, getCurrentInstance } from 'vue'
const msg = ref('Hello World!')
const isVapor = getCurrentInstance().vapor
</script>
<template>
<h1>{{ msg }}</h1>
<input v-model="msg" />
<b>VAPOR {{ isVapor ? 'ON' : 'OFF' }}</b>
</template>
被编译后的runtime:
js
import { template as _template, children as _children, vModelText as _vModelText, withDirectives as _withDirectives, on as _on, createTextNode as _createTextNode, append as _append, renderEffect as _renderEffect, setText as _setText } from 'vue/vapor';
function render(_ctx) {
const t0 = _template("<h1></h1><input><b>VAPOR </b>")
const n0 = t0()
const { 0: [n1], 1: [n2], 2: [n4],} = _children(n0)
_withDirectives(n2, [[_vModelText, () => _ctx.msg]])
_on(n2, "update:modelValue", $event => ((_ctx.msg) = $event))
const n3 = _createTextNode()
_append(n4, n3)
_renderEffect(() => {
_setText(n1, _ctx.msg)
})
_renderEffect(() => {
_setText(n3, _ctx.isVapor ? 'ON' : 'OFF')
})
return n0
}
首先创建一个静态节点的模板对象,然后从中获取每一个子节点,单独操作每个子节点动态绑定的部分,对于动态绑定会创建一个渲染的副作用函数 effect ,确保 effect 中依赖的响应式数据发生变化时,重新执行更新DOM的回调函数。
对于vapor mode而言,其中的核心是它的compile策略。
编译策略
由于vapor mode移除了 vnode 对象,导致无法让开发者手动写 render 函数,但是为了保持 Javascript 的灵活性,还是需要增加对于jsx
的支持,因此编译源对象就不仅仅包括template
,也必须要包含jsx
。
对于这两种vapor mode写法组件,经过 babel 的编译,最终都会生成自己的AST
,再将两种不同的类型的AST
进行操作,生成同一份vapor mode的代码,这种编译策略会包含很多类似的处理逻辑,同时需要维护两份compile code
,开发成本比较高;同时假想一下,如果还需要支持第三种开发模式,那岂不是很难受?!何解?
"计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。 ---某位计算机大佬说过"。既然vapor mode干掉了vnode中间层,那么就再加入一个替代的轻量的中间层------IR(intermediate representation),在计算机科学中表示为中间表示(语言)。
IR的特性
常用的IR包含后缀表示、图表示、三位址代码和LLVM IR,IR在编译流程中的位置与作用如下图所示:
现代高级编程语言中推出的更高级的IR实现:
在vapor mode中,IR就是作为template/jsx
到vapor code
的中间层表示:
IR的出现就解决了多种编译对象的代码统一生成问题,它在源码中的实现也就是一个基础的IR节点对象:
ts
export interface BaseIRNode {
/** 节点类型 */
type: IRNodeTypes
/** 源码位置 */
loc: SourceLocation
}
vapor mode的目标
项目的最终目标是可以在一个vue应用中混合使用vapor组件和non-vapor组件,可以通过增加.vapor
文件名后缀来区分。
最后达到你中有我,我中有你的境界~
总结
- vapor mode是一个无虚拟DOM的,以性能为目标的新的编译策略
- 它可以支持
template
和jsx
两种写法 - 最终可以实现在应用中自由组合vapor组件和non-vapor组件
思考
vue团队推出的vapor mode模式,其出发点是为了解决渲染性能问题,对于开发者来说肯定是利好消息,但是我对于最后的目标还是有一些疑惑。
我觉得它最终希望可以混合使用vapor组件和non-vapor组件,可能会增加一些额外的心智成本,哪些组件需要用vapor模式开发呢?如果单纯为了极致的渲染性能,干脆所有的组件全部使用vapor模式开发好了,那如果这样的话,是否可以直接在编译配置中增加一个"是否开启vapor mode"的开关,直接在应用的编译层面做选择,而不需要开发者在每个组件的开发上做对应的选择。主要问题应该在于vapor mode的运行时 api 会有所改动,导致无法直接作为vapor mode的编译对象。这只是我的个人想法,大家有什么其他的想法可以在评论区讨论~
期待vue-vapor早日完成,vue赛高!!!