大家好,我是宝哥。
在快速发展的网络开发世界中,创新的Vue.js团队给我们带来了Vapor Mode。这个新模式优化了Vue的核心渲染过程,帮助我们的应用程序像轻烟一样运行,开发者无需深入复杂的优化工作。
在这篇文章中,我们将揭示Vapor Mode如何优雅地提升应用效率,以及如何开始尝试使用它。但首先,让我们先弄清楚为什么要开发Vapor Mode。
为什么需要Vapor Mode?
如果你之前使用过JavaScript框架,你很可能熟悉虚拟DOM的概念。它涉及创建和更新DOM的虚拟表示,并将其存储在内存中以与实际DOM同步。由于更新VDOM比更新实际DOM要快,它为框架提供了相对低成本地对VDOM进行必要更改的自由。
在Vue中,其基于VDOM的渲染系统将我们模板部分的代码转换为实际的DOM节点。该系统还有效管理节点的变更,这些变更可以通过JavaScript函数、API调用等动态生成。
虽然VDOM提高了速度和性能,但在更新DOM时,仍然需要遍历节点树并比较每个虚拟节点的属性以确保准确性。这个过程还包括为树的每个部分生成新的VNodes,无论是否有变更,这可能导致不必要的内存压力。
但是,在Vue中,引入了另一种方法来解决这个问题,称为“编译器感知的虚拟DOM”。
这是一种混合方法,引入了一些优化概念来帮助解决这个问题,包括:
静态提升(Static Hoisting)
补丁标志(Patch Flags)
让我们更仔细地看看这些,以更清楚地了解Vue的渲染系统,这样我们就能更好地理解Vapor Mode带来了什么。
Vue中的静态提升
静态提升是一种技术,它自动从渲染函数中提取VNode创建,允许在多次重新渲染中重用VNodes。这种优化是有效的,因为这些VNodes随时间保持不变。
例如,给定这段代码:
<div><p class="vue">Vue.js是酷的</p><p class="solid">Solid.js也是酷的</p><p>同意吗?{{agree}}</p>
</div>
当使用静态提升技术编译时,我们得到:
import { createElementVNode as _createElementVNode, ... } from "vue"
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", { class: "vue" }, "Vue.js是酷的", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createElementVNode("p", { class: "solid" }, "Solid.js也是酷的", -1 /* HOISTED */)
// ... 其他代码
在上面的例子中,你会看到有两个变量:_hoisted_1
和 _hoisted_2
。它们包含将保持不变的静态代码,这些代码被提升或从渲染函数中拉出来,以避免重新处理不是动态的代码。
我们声明并重新渲染最后一个 p
标签中的元素,因为该元素包含一个动态变量,这个变量随时可能改变。
值得注意的是,当有足够的连续静态元素时,它们将被合并为一个单一的静态Vnode(使用 createStaticVNode
),并传递给渲染函数。
让我们看一个例子:
<div><p class="vue">Vue.js是酷的</p><p class="solid">Solid.js也是酷的</p><p class="vue">Vue.js是酷的</p><p class="solid">Solid.js也是酷的</p><p class="solid">React也很酷</p><p>{{agree}}</p>
</div>
当编译时,我们得到:
import { createElementVNode as _createElementVNode, ... } from "vue"
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<p class=\\"vue\\">Vue.js是酷的</p><p class=\\"solid\\">Solid.js也是酷的</p>..., 5)
// ... 其他代码
现在,我们不仅有多个hoisted
常量,而只有一个包含模板所有静态代码的常量。
Vue中的补丁标志
补丁标志允许Vue智能地更新DOM。它们用于标识具有动态绑定的元素所需的更新类型,例如class
、id
、value
等。与全面更新方法不同,它只根据这些标志选择性地更新已更改的内容,而无需重新渲染整个组件或检查每个元素。
这不仅通过只关注已更改的元素来加快更新过程,而且还避免了不必要的操作,比如调整未更改的元素的顺序。
这是通过在更新时将VNode传递给渲染函数来完成的。createElementVNode
函数接受一个数字作为其最后一个参数。这个数字表示一个补丁标志,它指示在调用渲染函数时需要更新的动态绑定的类型。
让我们看看这个在行动中的例子:
<div :class="{ active }"></div>
<input :id="id" :value="value" :placeholder="placeholder">
<div>{{ dynamic }}</div>
这里,我们有一个带有动态类active
的div
,一个带有动态id
、value
和placeholder
的input
元素,还有一个带有dynamic
文本的div
。
当这段代码被编译时,我们得到这个:
import { normalizeClass as _normalizeClass, ... } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createElementBlock(_Fragment, null, [_createElementVNode("div", {class: _normalizeClass({ active: _ctx.active })}, null, 2 /* CLASS */),// ... 其他代码], 64 /* STABLE_FRAGMENT */))
}
在这里,每个createElementVNode
函数接受一个数字,该数字表示作为其最后一个参数的属性。第一个数字是2
,表示一个类,8
表示属性,64
表示一个稳定的片段。你可以在GitHub上找到每个标志的完整列表。
通过这种方法,Vue可以相对于React和Svelte表现得更好,如下所示图表。
Vapor Mode的理由
尽管Vue的方法已经比较精细,但仍存在一些性能问题。这些问题包括不必要的内存使用、树差异比较以及VDOM的缺陷。
Vapor Mode的创建就是为了解决这些问题。
Vapor模式是一种替代的编译策略,旨在通过将代码编译成更高效的JavaScript输出,使用更少的内存,减少运行时支持代码,并避免上面说明的编译器感知的VDOM方法的缺陷,从而提高你的Vue.js应用程序的性能。
Vapor Mode的一些好处包括:
它是可选的,不影响现有的代码库。这意味着你可以立即开始使用Vapor Mode来优化你的Vue 3应用程序的性能,而无需对代码进行任何更改。
如果应用程序中只使用Vapor组件,你可以完全从捆绑包中删除VDOM运行时,减少基础运行时大小。
❕ Vapor模式将只支持组合API和<script setup>
Vue的Vapor Mode如何工作
根据Vue(和Vite.js)的创建者Evan You的说法,Vapor模式受到Solid.js的启发,Solid.js是一种用于创建用户界面的声明性JavaScript库,它采用了一种不同的编译和渲染节点的方法。
与使用虚拟DOM不同,它将模板编译为真实的DOM节点,并使用细粒度的反应进行更新。像Solid一样,Vue在其反应性系统中使用代理和基于读取的自动跟踪。
给出我们上一个例子中的相同代码,开启Vapor Mode时,它编译并给我们:
import { renderEffect as _renderEffect, ... } from 'vue/vapor'
const t0 = _template("<div></div>")
const t1 = _template("<input>")
export function render(_ctx) {const n0 = t0()const n1 = t1()const n2 = t0()_renderEffect(() => _setClass(n0, { active }))_renderEffect(() => _setDynamicProp(n1, "id", id))// ... 其他代码return [n0, n1, n2]
}
在编译后的代码中,你会看到来自vue/vapor
包的renderEffect
、setClass
、setDynamicProp
、setText
和template
的导入。
让我们看看每个函数的作用。
renderEffect:此函数负责监听类、属性和文本的更改,以确保在更新时对这些节点进行正确的更改。
setClass:顾名思义,此函数将类分配给节点元素。它接受两个参数:一个
element
(或node
)和它分配给元素的class
。setDynamicProp:此函数用于设置元素上的动态属性。它需要三个参数:
element
、key
和value
。这些用于确定每次调用此函数时分配或更新的适当值。setText:此函数接受一个
node
和可能的值。它将给定的值设置为节点的textContent
,同时还验证内容是否已被修改。template:此函数接受一个有效的HTML字符串并从中创建一个元素。检查该函数时,我们可以看到它使用基本的DOM操作方法。具体来说,使用
document.createElement
创建元素。然后使用innerHTML
追加元素的内容,innerHTML
接受HTML字符串。
通过这些函数的组合,Vue可以将你的组件和应用程序编译成更快、更高效的代码,最终提高应用程序的性能和捆绑包大小。
为了帮助开发者熟悉Vapor Mode,Vue团队发布了一个演示 和模板浏览器。
示例允许你比较启用和未启用Vapor模式时代码的编译版本。
在示例内,你可以检查代码的CSS、JS和SSR输出。它还允许你切换Vapor模式功能,轻松比较输出的差异。
模板浏览器类似于示例,但它只提供代码的JavaScript输出,并提供一些选项,如SSR、模块等。
使用Vapor Mode
根据Vapor Mode仓库,这里有一个使用Vapor模式构建组件的示例:
<script setup lang="ts">
import {onBeforeMount,onMounted,onBeforeUnmount,onUnmounted,ref,
} from 'vue/vapor'const bar = ref('update')
const id = ref('id')
const p = ref<any>({bar,id: 'not id',test: 100,
})function update() {bar.value = 'updated'p.value.foo = 'updated foo'p.value.newAttr = 'new attr'id.value = 'updated id'
}function update2() {delete p.value.test
}onBeforeMount(() => console.log('root: before mount'))
onMounted(() => console.log('root: mounted'))
onBeforeUnmount(() => console.log('root: before unmount'))
onUnmounted(() => console.log('root: unmounted'))
</script><template><div>root comp<button @click="update">update</button><button @click="update2">update2</button><input :value="p.test" :placeholder="p.bar" :id="p.id" /></div>
</template>
与Vue开发者习惯的方式不同,注意我们是如何在vue/vapor
包中导入ref
、onBeforeMount
、onMounted
和其他函数的。
这些函数都是组合API的一部分,唯一的区别是它们现在从vapor包导入,该包不依赖于VDOM。
这允许我们在应用程序中使用Vapor Mode组件和非Vapor Mode组件,而无需额外配置。
支持的功能
作为提高性能和降低基础运行时大小的努力的一部分,Vapor Mode将只支持组合API,并且只能与<script setup>
一起使用。
随着Vue团队的持续工作,我们将看到Vapor Mode支持的功能的更多示例,但有一点是明确的:Vapor Mode组件中支持的功能将与非Vapor模式组件的工作方式相同。
总结
现在我们已经看到了Vue当前如何使用编译器感知的虚拟DOM方法编译代码及其缺点,我们将看到Vapor Mode的性能如何,以及它如何实现更小的捆绑包大小和改进的性能的承诺。随着我们继续等待发布日期,熟悉Vapor Mode的可用功能是很重要的,这可以通过Vapor演示完成。
最后,如果你觉得宝哥的分享还算实在,就给我点个赞,关注一波。分享出去,也许你的转发能给别人带来一点启发。
关注我,加星标,明天见!
Vue 3 将推出新特性,可以抛弃虚拟DOM了!
关注下方宝哥微信,进宝哥前端开发11群,
获取我公众号整理的所有资料,
包括前端电子书,面试资料,简历模板和副业资料等!
以上,如果本文对你有所启发,欢迎点“在看、点赞”支持下吧!