人人都会AI编程

28.1 Vue 3 破坏性变更总览

更新时间:2026-07-09

从 Vue 2 升级到 Vue 3,并不是简单地换一个版本号。底层响应式系统的重写、框架 API 的重新设计以及部分特性的移除,带来了一系列需要开发者主动适配的破坏性变更。好在这些变更大多有规律可循,本节将最常遇到的变更整理成一份速查清单,帮助你快速评估迁移成本。


一、全局 API 的调用方式变了

Vue 2 中通过 Vue.xxx 直接修改全局行为,这在多应用实例场景下会造成污染。Vue 3 引入了 应用实例 的概念,任何全局配置都挂载在 createApp 返回的实例上。

Vue 2 → Vue 3 对照:

// Vue 2
import Vue from 'vue'
Vue.config.ignoredElements = ['my-el']
Vue.component('MyButton', MyButton)
Vue.prototype.$http = axios
new Vue({ render: h => h(App) }).$mount('#app')

// Vue 3
import { createApp } from 'vue'
const app = createApp(App)
app.config.compilerOptions.isCustomElement = tag => tag.startsWith('my-')
app.component('MyButton', MyButton)
app.config.globalProperties.$http = axios  // 替代 Vue.prototype
app.mount('#app')

影响面:所有通过 Vue.prototype 挂载的工具方法/属性(如 $http$bus)需要改为 app.config.globalPropertiesVue.componentVue.directive 等需通过应用实例调用。


二、响应式系统从 Object.defineProperty 改为 Proxy

这是一项彻底的底层升级,对开发者来说是几乎完全的利好,但也带来了几个细微的行为差异。

解决的历史痛点:

  • 对象新增/删除属性自动响应(不再需要 Vue.set / Vue.delete
  • 数组索引修改和 length 赋值可直接响应
  • 深层嵌套对象的初始化性能更好

Vue 3 中的变动点:

  1. reactive 对象解构后不再自动保持响应式

const { name } = reactive({ name: 'vue' }) 得到的 name 是一个普通字符串。解决方式是使用 toRefs 或继续通过原对象访问。

  1. ref 在模板中自动解包,但在 setupreturnreactive 中不会

这一点 Vue 3 通过编译器和 proxy 做了大量屏蔽,但在组合式函数中返回 ref 时需要显式 .value

  1. this 不再自动响应式代理

如果你在选项式 API 中将数据放在 data 之外,Vue 2 会将其变为响应式,Vue 3 不会。所有响应式数据必须来自 datasetup 等标准入口。


三、v-model 双向绑定重新设计

Vue 2 中,一个组件只能有一个 v-model(对应 value prop 和 input 事件),如果要绑定多个值,需要用 .sync 修饰符。Vue 3 统一了这两套机制。

变化:

  • 默认 v-model 对应的 prop 从 value 改为 modelValue,事件从 input 改为 update:modelValue
  • 支持 多个 v-model 绑定v-model:title="title" 会使用 title prop 和 update:title 事件。
  • .sync 修饰符被移除,原来 .sync 的场景直接用 v-model:propName 替代。

迁移示例:

<!-- Vue 2:单个 v-model + .sync -->
<MyInput v-model="text" :visible.sync="show" />

<!-- Vue 3:多 v-model -->
<MyInput v-model="text" v-model:visible="show" />

注意:自定义组件的 v-model 用法需要更新 propsemits 声明,但大部分 UI 组件库(Element Plus 等)已经做了适配。


四、过滤器(Filters)被彻底移除

Vue 2 中可以在模板中使用 {{ message | capitalize }} 这种管道符语法,Vue 3 不再支持。官方认为过滤器的功能完全可以被计算属性方法调用所替代,而且更清晰。

迁移方案:将过滤器定义为全局或局部方法,在模板中直接调用:

<!-- Vue 2 过滤器 -->
{{ price | currency }}

<!-- Vue 3 替代方案 -->
{{ currency(price) }}

五、事件总线 API 被移除($on / $off / $once

Vue 2 允许使用 new Vue() 作为事件总线,实现跨组件通信。Vue 3 的应用实例不再实现事件发射器接口,因此这三个方法不可用。

替代方案

  • 父子通信用 props + emits
  • 跨层级通信用 provide / inject
  • 全局状态管理用 PiniaVuex
  • 强依赖事件总线场景可引入第三方库(如 mitt

六、生命周期钩子更名

Vue 3 对两个生命周期的命名进行了语义化调整,旧名称依旧能用(在选项式 API 中,直到某个版本后才废弃),但推荐尽快迁移。

| Vue 2 选项式 | Vue 3 选项式 | 说明 |
|--------------|--------------|------|
| beforeDestroy | beforeUnmount | 组件销毁前 |
| destroyed | unmounted | 组件销毁后 |

组合式 API 中对应钩子分别为 onBeforeUnmountonUnmounted


七、v-ifv-for 优先级逆转

Vue 2 中,同一个元素上同时使用 v-forv-if 时,v-for 优先级更高。这导致了一些潜在的性能陷阱(如需要先循环再判断隐藏)。Vue 3 反过来:v-if 优先级高于 v-for

这意味着 v-if 无法访问 v-for 内的迭代变量。官方强烈建议永远不要将两者放在同一个元素上,最佳实践是:

  • 先用 computed 过滤列表,再对过滤后的数据 v-for
  • 或者在 v-for 外层加一个 <template> 放置 v-if

八、自定义指令钩子函数对齐组件生命周期

自定义指令的钩子名称从 Vue 2 的独立命名(bindinsertedupdate 等)改为与组件生命周期对齐的命名。

| Vue 2 | Vue 3 | 触发时机 |
|-------|-------|----------|
| bind | beforeMount | 指令首次绑定到元素时 |
| inserted | mounted | 元素被插入父节点时 |
| update | 移除,用 updated | 组件更新时(钩子可接收新旧值) |
| componentUpdated | updated | 组件及子组件更新后 |
| unbind | unmounted | 指令与元素解绑时 |

同时,钩子的参数也发生了变化:vnode 被替换为当前组件实例 binding.instance


九、其他常见的细节变化

  1. $listeners 被合并到 $attrs

Vue 2 中,$attrs 只包含未声明的 props,而 $listeners 包含所有未在 emits 中声明的事件监听器。Vue 3 移除了 $listeners,所有传递下来的 props 和事件(都以 onXxx 形式)都在 $attrs 中。这对封装第三方组件时的属性透传有较大影响。

  1. keyCode 按键修饰符移除

不再支持 @keyup.13 这种写法,只能使用官方别名(如 @keyup.enter)。

  1. 过渡类名修改

CSS 类名从 v-enterv-leave 改为 v-enter-fromv-leave-from,避免与旧的 v-enter-active 产生语义混淆。

  1. 渲染函数 API 全面导入

render 函数不再自动注入 h,需手动从 vue 导入;VNode 的属性结构变为扁平化(如不再有 class 对象,而是直接传 class 属性)。

  1. 移除 $children

在 Vue 2 中可以通过 this.$children 访问所有子组件实例,这在 Vue 3 中已不再提倡。组件通信应使用 props、emits、ref 或 provide/inject。


迁移策略建议

面对这些变更,不必一次性全部改完。官方的 @vue/compat 兼容构建允许在运行时模拟大部分 Vue 2 行为,并在控制台给出废弃警告,帮助渐进式升级。建议流程:

  1. 使用 @vue/compat 将项目升级到 Vue 3 运行时,确保应用能正常启动。
  2. 根据控制台警告,逐项修复不兼容代码。
  3. 最终移除兼容包,完成纯净的 Vue 3 构建。

这份总览的目的不是让你把所有 API 背下来,而是在遇到“为什么代码在 Vue 3 里挂了”时,能快速定位到对应的破坏性变更,找到正确的修改方向。下一节将介绍具体迁移工具和分步实施方法。