Skip to content

Vue Vapor Mode 解析:Vue 3.6 带来了哪些变化 ​

译注:本文编译自 dev.to 文章《Vue Vapor Mode: what it is and what changes in Vue 3.6》,原文发布于 2026 年 10 月 6 日。原文链接见文末。

Vue 3.6 目前尚未稳定。截至 2026 年 10 月 6 日,最新版本为 3.6.0-rc.10(9 月 30 日发布),稳定版仍停留在 3.5.43(9 月 17 日发布)。这一信息来自 npm 与 vuejs/core 仓库的 release 记录。

Vue Vapor Mode 是 3.6 的招牌特性:一种跳过虚拟 DOM、直接生成 DOM 更新代码的编译模式。它是可选的(opt-in),因此你可以自行决定哪些组件使用它,无需重写整个应用。本文将介绍 Vapor 的工作原理、如何在 Vue 与 Nuxt 4.6 中启用、目前尚不支持的内容,以及何时值得一试。

Vapor 的工作原理 ​

在传统模式下,当响应式状态变化时,Vue 会执行渲染函数,构建一棵新的 VNode 树(描述 UI 的普通 JavaScript 对象),与上一棵树进行比对,然后才操作真实 DOM。

在 Vapor Mode 下,编译器分析模板并生成直接操作 DOM 的代码。Vapor 组件在运行时不会创建 VNode,也不做树比对。当某个响应式值变化时,只有绑定该值的 DOM 部分会被更新。

根据 Vue 3.6 官方 release notes,其目标是更小的基础包体积和更好的性能。同一份说明称,在第三方基准测试(js-framework-benchmark)中,Vapor 达到了与 Solid 和 Svelte 5 相同的性能水平。与任何基准测试一样,结果取决于所测量的场景。

Alien Signals 是什么? ​

Vue 3.6 还包含基于 alien-signals 库对 @vue/reactivity 的一次重大重构。rc.1 的说明称其提升了响应式系统的性能和内存占用。这是一项内部改动:ref、reactive、computed 和 watch 的 API 保持不变。由于它是 Vue 3.6 的一部分,无论是否使用 Vapor 都能获得这一改进。

如何启用 Vapor Mode ​

Vapor 适用于使用 <script setup> 或仅含模板的 SFC。你需要在组件上标记 vapor 属性:

vue
<script setup vapor lang="ts">
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">
    count is &#123;&#123; count &#125;&#125;
  </button>
</template>

在 Nuxt 之外,@vitejs/plugin-vue 负责编译 Vapor SFC(该插件的 changelog 记录了 Vapor 组件支持,包括仅含模板的组件),因此请使用较新的版本。

Vue 也接受 <script vapor> 作为 <script setup vapor> 的简写。你也可以把标记放在模板上,写成 <template vapor>,从而将整个 SFC 以 Vapor 模式编译。

在现有应用中使用 Vapor 组件 ​

在使用 createApp() 创建的应用中,需要安装 vaporInteropPlugin 才能使用 Vapor 组件:

typescript
import { createApp, vaporInteropPlugin } from 'vue'
import App from './App.vue'

createApp(App).use(vaporInteropPlugin).mount('#app')

安装该插件后,Vapor 组件与普通组件可以相互嵌套。Vue 的说明提醒,这覆盖了标准用法下的 props、事件和插槽,但尚未覆盖所有边界情况。官方建议在应用中划分清晰的区域,每个区域使用一种模式,并尽量避免混合嵌套。

全 Vapor 应用 ​

如果所有组件都是 Vapor,可使用 createVaporApp():

typescript
import { createVaporApp } from 'vue'
import App from './App.vue'

createVaporApp(App).mount('#app')

这样虚拟 DOM 运行时就不会进入打包产物。每个组件仍需 vapor 标记。用渲染函数或 JSX 编写的组件仍属于虚拟 DOM 组件,需要 interop。Vapor 应用也可以安装 vaporInteropPlugin 来接受虚拟 DOM 组件,但这会把运行时重新拉回来,削弱包体积收益。

Nuxt 4.6 中的 Vapor ​

2026 年 10 月 5 日发布的 Nuxt 4.6 支持 interop 模式下的 Vapor。应用根节点仍基于虚拟 DOM,由你选择哪些组件或页面使用 Vapor。要启用它,在 nuxt.config.ts 中打开该选项:

typescript
export default defineNuxtConfig({
  vue: {
    vapor: true,
  },
})

然后用 <script setup vapor> 标记组件,如上面的示例所示。Nuxt 要求 Vue ^3.6.0-rc.2 或更高版本。根据发布文章,路由、useAsyncData、布局以及大多数内置组件均可保持不变地工作。文章建议先在新项目中尝试 Vapor。

Nuxt 文档列出了 interop 模式的若干限制:

  • 应用根节点不能是 Vapor,因为 Nuxt 中不支持全 Vapor 应用;
  • Vapor 组件不会在模板 ref 上暴露 $el;
  • <ClientOnly> 会跳过其属性转发,而 <NuxtIsland>、服务端组件以及读取文本子节点的 head 组件(<Title>、<Style> 和 <Noscript>)在传入 Vapor 插槽时会发出警告;
  • 带 key 的 onPrehydrate 会回退到无 key 形式(并在开发环境警告),因为没有组件实例可挂载 key;
  • 通过 defineNuxtComponent 使用 asyncData 和 fetchKey 不可用(请在 <script setup> 中使用 useAsyncData);
  • 在 await 之后调用的 Nuxt composables 可能丢失上下文。

同一篇发布文章给出了 Nuxt 4.6 的性能数据:一个 starter 项目的 nuxt build 从 4.4 秒降至 3.6 秒(快 18%),一个包含 300 个 <NuxtLink> 的页面在 SSR 下从 110 req/s 提升到 140 req/s(+26%)。Nuxt 将这些提升归功于该版本的通用优化,而非 Vapor。

如果你仍在使用 Nuxt 3,它已于 2026 年 7 月 31 日停止维护。

Vapor 目前尚不支持的内容 ​

Vapor 支持 Vue API 的一个子集。官方说明列出了 Vapor 组件中不可用或不适用的事项:

  • Options API(data、methods、mounted 等);
  • app.config.globalProperties;
  • getCurrentInstance(),它会返回 null;
  • 逐元素生命周期事件,如 @vue:mounted;
  • v-memo;
  • 组件模板 ref 上的 $el、$props、$attrs、$slots 和 $refs。

基于虚拟 DOM 构建的组件库也值得测试。说明中提到在 Vapor 内使用虚拟 DOM 组件库时存在「粗糙边缘」,但未点名任何库。如果某个库依赖 getCurrentInstance(),上述 null 可能成为问题,但只有在你自己的项目中测试才能确定。

官方说明没有将 <Suspense> 或 Vue DevTools 列为与 Vapor 不兼容,但这并不保证完全支持。RC changelog 记录了若干针对 Vapor 与 Suspense 集成的修复,说明支持仍在完善中。在决定之前,请在自己的环境中测试两者。

SSR 也是如此。RC changelog 中有若干针对 Vapor 组件的 hydration 修复,这表明 SSR 已获支持但仍在调优。如果你的应用在服务端渲染,请仔细测试 hydration。

事件与 .delegate ​

在较早的 RC 中,Vapor 将事件委托到 document,因此祖先元素上的 stopPropagation() 可能阻止子元素的事件处理器。自 rc.2 起,监听器像常规 Vue 一样直接绑定到元素上,委托改为通过 .delegate 修饰符选择性启用:

vue
<button @click.delegate="onClick" />

compilerOptions.eventDelegation 选项已被移除。

自定义指令 ​

自定义指令在 Vapor 中也有不同的接口。你不再编写带钩子的对象,而是编写一个接收元素和响应式 getter 的函数,并可返回清理函数:

typescript
import { watchEffect } from 'vue'

const MyDirective = (el, source) => {
  watchEffect(() => {
    el.textContent = source()
  })
  return () => console.log('cleanup')
}

完整签名为 (node, value?, argument?, modifiers?),其中 value 是响应式 getter。

Vapor 在哪些场景有帮助 ​

收益出现在应用进行大量 DOM 更新工作的场景:密集的列表和表格、每秒变化多次的组件,以及初始 JavaScript 体积重要的页面。对于普通的 CRUD 应用,用户很可能察觉不到差异。

Vapor 也无法解决并非来自渲染器的问题。在 DOM 中渲染 10,000 个条目在两种模式下都很慢,解决办法是虚拟化列表。直接在模板中调用重函数,如 &#123;&#123; calculateTotal(item) &#125;&#125;,在两种模式下都仍是坏做法。

现在应该采用吗? ​

Vue 团队表示 Vapor 的功能集在 RC 中已完整,并建议目前有两种用法:将 Vapor 应用于现有应用的部分区域(例如性能敏感的页面),以及完全用 Vapor 构建小型新应用。

对于已在 Vue 3.5 上运行良好的生产应用,等待稳定版发布是更稳妥的选择。对于已测量出瓶颈的孤立页面,在 Nuxt 4.6 或带 interop 的 Vue 应用中进行试验是合理的尝试。开始之前,请确认:

  • 你已测量当前性能(浏览器 Performance 面板即可)并发现渲染瓶颈;
  • 该组件使用 <script setup> 或仅含模板;
  • 它依赖的 UI 库在你的项目中能与 interop 配合工作;
  • 你能将 Vapor 隔离到单个路由或组件,并能轻松回退。

原文链接 ​

https://dev.to/codigoaoponto/vue-vapor-mode-what-it-is-and-what-changes-in-vue-36-2cmc