Vue 版本演进:Vue 2 与 Vue 3 全对比
接手维护一个老项目,进去一看 package.json 里躺着 vue: ^2.6.14;自己新起的项目用的却是 Vue 3。两套代码摆在一起,写法完全两个时代:老项目里 data/methods/computed 分家、新增属性还要 Vue.set;新项目一个 setup() 全搞定、reactive 随手加属性就响应。
明明都叫 Vue,为什么差别这么大?因为中间隔了一次内核级重写。这篇把 Vue 的版本演进和 Vue 2 → Vue 3 的差异系统梳理一遍,看完你就能回答:为什么会有 Vue 3、老项目要不要升、新项目怎么选。
版本时间线:三个时代
| 版本 | 时间 | 定位 |
|---|---|---|
| Vue 1.x | 2014 | 初版,模板引擎 + 数据绑定,小步试水 |
| Vue 2.x | 2016–2023 | 统治前端近十年,官方最后版本 2.7.16(2022 年发布) |
| Vue 3.x | 2020–至今 | 内核全面重写,当前唯一活跃主线(3.5+) |
关键时间点:Vue 2.7(2022)是 2.x 的终点,官方把组合式 API 回迁到 2.7 里当「兼容桥」,让大家在升级到 3 之前就能用新写法;Vue 2 官方支持已于 2023 年底停止。
Vue 2 的三大硬伤:为什么必须重写
Vue 3 不是小修小补,是推倒重来。Vue 2 有三个被吐槽最多的根子上的问题:
- 响应式天花板:基于
Object.defineProperty,只能拦截「已存在的属性」。新增属性、删除属性、数组按索引赋值全都不响应——于是被迫发明Vue.set/$delete/splice一堆补丁 API,且每次操作还得递归遍历,深层对象性能开销大。 - 逻辑复用靠 mixin:多个组件共用一段逻辑,只能 mixin 进去,命名冲突、来源不明,代码一多就乱。
- TypeScript 支持弱:Vue 2 源码是 JS 写的,类型全靠社区补丁(vue-class-component / vue-property-decorator),体验远不如人意。
这三个问题正是 Vue 3 设计时重点解决的。下面看差异到底有多大。
Vue 2 vs Vue 3:核心差异对照表
| 维度 | Vue 2 | Vue 3 |
|---|---|---|
| 响应式原理 | Object.defineProperty 逐个拦截已有属性 | Proxy 对象级代理,全量拦截 |
| 新增/删除属性 | ❌ 不响应,必须 Vue.set / $delete | ✅ 直接响应 |
| 数组变更 | 重写 7 个变异方法,索引赋值不响应 | ✅ 索引赋值、length 修改全响应 |
| 编写 API | Options API(data/methods/computed 分家) | Composition API(setup 内按逻辑组织) |
| 逻辑复用 | mixin(命名冲突、来源不明) | composable(纯函数,天然隔离) |
| TypeScript | 支持弱,靠社区补丁 | 源码 TS 重写,一等公民 |
| 官方状态库 | Vuex | Pinia(Vuex 5 前身) |
| Vue Router | 3.x | 4.x(API 基本兼容) |
| 虚拟 DOM | 传统 diff | 重写:patchFlag、静态提升、事件缓存 |
| 包体积 | 完整版约 30KB+ | tree-shaking 支持,按需约减半 |
| 多根节点 | ❌ 组件必须单根 | ✅ Fragment,支持多根 |
| 全局 API | Vue.xxx(Vue.use / Vue.component) | app.xxx(createApp 实例化) |
上面「响应式」两行的差异是实跑验证过的(vue 2.7.16 与 @vue/reactivity 3.x,Node 实跑 9 断言全 PASS):
// Vue 2:新增属性不响应,必须 Vue.set
const obj = Vue.observable({ count: 1 });
obj.newKey = 42; // 无 getter/setter,改了视图不更新
Vue.set(obj, 'newKey', 42); // 正确姿势
// Vue 3:reactive 后新增/删除属性直接响应
const obj = reactive({ count: 1 });
obj.newKey = 42; // 直接生效
delete obj.count; // 也会触发更新defineProperty 和 Proxy 的机制细节,见《Vue 响应式原理详解》——那里用 12 个断言实跑演示了「为什么 Vue 2 要 $set、Vue 3 不用」。
破坏性变更:升级到 Vue 3 要改什么
升级不是平滑的,下面是迁移时最容易踩的几处:
| 变更点 | Vue 2 | Vue 3 |
|---|---|---|
| 全局 API | Vue.use(VueRouter) | app.use(router),全局 API 移到应用实例 |
| 事件总线 | $on / $off / $emit 全局事件 | 移除,改用 mitt 等第三方库 |
| filters | price | currency 模板过滤器(Vue 2 独有) | 移除,改函数或 computed |
| v-model | 组件上 value + input | modelValue + update:modelValue,支持多个 v-model |
| 自定义指令钩子 | bind / inserted / update | beforeMount / mounted / updated 统一对齐生命周期 |
| 异步组件 | Vue.component('x', () => import(...)) | defineAsyncComponent |
| 插槽语法 | 具名插槽 slot="name" | v-slot 统一(简写 #name) |
this 指向 | Options 里 this 指向组件实例 | setup 里没有 this,用 ref/reactive 替代 |
最伤的是 filters 移除和 $on/$off 移除——老项目里这两个用得越狠,迁移成本越高,这也是很多团队选择「老项目停在 2.7」的原因。
怎么选:新项目 / 老项目
- 新项目:无脑 Vue 3。官方只维护 3.x,配套 Vite 开发体验、Pinia、组合式 API 生态全在新版,没有任何理由回头。
- 老项目在 Vue 2 且没到 2.7:先升到 2.7(成本低,无破坏性变更),拿到组合式 API 和更好的 TS 支持,用新写法逐步重构,为将来升 3 铺路。
- 老项目在 2.7 且业务稳定:可以不升 3——但要知道 2.7 已停止官方支持,依赖的安全漏洞要靠自己兜底。
- 真要升 3:用官方迁移构建版本 + 迁移工具逐步处理破坏性变更,组件逐个迁移,别一次推倒。
Composition API 的写法和 mixin → composable 的演进,见《Vue Composition API 详解》;Vue 3 的模板语法、组件化怎么上手,看《Vue 快速入门》;组件之间怎么通信,看《Vue 组件通信全景》。
