公司老项目用 Vue2,新项目要上 Vue3。Vue3 不只是版本号变了,从响应式原理到 API 风格都有重写。本文系统对比两者的差异,方便迁移和理解。
一、响应式原理(最核心的区别)
这是 Vue2 和 Vue3 性能差异的根源。
Vue2:Object.defineProperty
// Vue2 的响应式本质
Object.defineProperty(obj, 'key', {
get() { /* 收集依赖 */ },
set() { /* 触发更新 */ }
})
痛点:
- 无法监测新增/删除属性:
this.obj.newProp = 1不会触发更新,必须用this.$set - 无法监测数组下标修改:
this.arr[0] = 1不生效,要用Vue.set或splice - 深度监听要一次性递归:对象初始化时就把所有层级遍历一遍,性能差
Vue3:Proxy
// Vue3 的响应式本质
const proxy = new Proxy(target, {
get(target, key) { /* 收集依赖 */ },
set(target, key, value) { /* 触发更新 */ }
})
优势:
- 监测新增/删除属性:直接
obj.newProp = 1就能触发,告别$set - 数组下标修改生效:
arr[0] = 1正常工作 - 惰性响应式:访问到哪一层才代理哪一层,初始化快
- 能监测 Map/Set/WeakMap 等新数据结构
二、API 风格
Vue2:Options API(选项式)
<script>
export default {
data() {
return { count: 0, name: '张三' }
},
computed: {
double() { return this.count * 2 }
},
methods: {
increment() { this.count++ }
},
mounted() {
console.log('组件挂载')
}
}
</script>
特点:data、methods、computed、生命周期分散在不同选项里。同一个逻辑的代码被拆得到处都是。
Vue3:Composition API(组合式)
<script setup>
import { ref, computed, onMounted } from 'vue'
const count = ref(0)
const name = ref('张三')
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
onMounted(() => {
console.log('组件挂载')
})
</script>
特点:
<script setup>语法糖,不用写export default- 相关逻辑写在一起,方便提取成自定义 Hook
- 没有
this,避免this指向混乱的问题
提取逻辑复用
Vue2:用 mixin,但 mixin 有命名冲突、来源不清晰的问题。
Vue3:自定义 Hook,清晰且无冲突:
// useCounter.js
import { ref } from 'vue'
export function useCounter(initial = 0) {
const count = ref(initial)
const increment = () => count.value++
return { count, increment }
}
// 组件中使用
const { count, increment } = useCounter(10)
三、响应式 API 对比
| 功能 | Vue2 | Vue3 |
|---|---|---|
| 简单变量响应式 | 放 data 里 | ref() |
| 对象响应式 | 放 data 里 | reactive() |
| 计算属性 | computed: {} | computed(() => ...) |
| 监听 | watch: {} | watch() / watchEffect() |
| 修改值 | this.count++ | count.value++(ref) |
ref vs reactive
import { ref, reactive } from 'vue'
// ref:用于基本类型,.value 访问
const count = ref(0)
count.value++
// reactive:用于对象,直接访问
const state = reactive({ name: '张三', age: 20 })
state.age++
注意:reactive 不能整体替换,state = newObj 会丢失响应式。要替换整体用 ref。
四、其他重要变化
1. 多根节点(Fragment)
Vue2 模板必须有单一根节点:
<!-- Vue2:报错 -->
<template>
<div>1</div>
<div>2</div>
</template>
<!-- Vue2:必须包一层 -->
<template>
<div>
<div>1</div>
<div>2</div>
</div>
</template>
Vue3 支持多根节点:
<!-- Vue3:可以 -->
<template>
<div>1</div>
<div>2</div>
</template>
2. Teleport(传送门)
把组件渲染到 DOM 树的其他位置,比如弹窗渲染到 body:
<Teleport to="body">
<div class="modal">弹窗内容</div>
</Teleport>
3. 生命周期变化
| Vue2 | Vue3 (setup) |
|---|---|
| beforeCreate | (setup 本身) |
| created | (setup 本身) |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeDestroy | onBeforeUnmount |
| destroyed | onUnmounted |
注意 destroyed 改名为 unmounted。
4. v-model 变化
<!-- Vue2 -->
<MyInput v-model="value" />
<!-- 等价于 -->
<MyInput :value="value" @input="value = $event" />
<!-- Vue3:默认用 modelValue -->
<MyInput v-model="value" />
<!-- 等价于 -->
<MyInput :modelValue="value" @update:modelValue="value = $event" />
<!-- Vue3 还支持多个 v-model -->
<MyInput v-model:name="name" v-model:age="age" />
5. 全局 API 变化
// Vue2
Vue.component('MyComp', {})
Vue.use(MyPlugin)
new Vue({ render }).$mount('#app')
// Vue3:改为应用实例
const app = createApp({})
app.component('MyComp', {})
app.use(MyPlugin)
app.mount('#app')
好处:多个应用实例互不干扰。
五、性能与体积
| 指标 | Vue2 | Vue3 |
|---|---|---|
| 包体积 | 较大 | 减少 ~40% |
| 首次渲染 | - | 快 ~100% |
| 更新渲染 | - | 快 ~50% |
| 内存占用 | - | 减少 ~50% |
| Tree-shaking | 不支持 | 支持(按需引入) |
Vue3 用 TypeScript 重写,类型支持更好,且支持 Tree-shaking —— 没用到的 API 不会打包进去。
六、生态系统
| 工具 | Vue2 | Vue3 |
|---|---|---|
| 构建 | Vue CLI (webpack) | Vite(推荐)/ Vue CLI |
| 路由 | vue-router 3 | vue-router 4 |
| 状态管理 | Vuex | Pinia(推荐)/ Vuex 4 |
| IDE 支持 | Vetur | Volar |
| UI 库 | Element UI | Element Plus / Naive UI |
Pinia 是 Vue3 官方推荐的状态管理,比 Vuex 简单很多:
// Pinia store
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
})
没有 mutations,直接修改 state,TypeScript 支持也更好。
七、迁移建议
新项目
直接用 Vue3 + Vite + Pinia,没有任何理由用 Vue2。
老项目迁移
- 不必一次性全迁:Vue3 提供了
@vue/compat迁移构建版本,可以渐进迁移 - 优先迁大组件:把逻辑复杂的组件用 Composition API 重写
- 工具链先行:Vetur → Volar,Vue CLI → Vite
- 第三方库检查:确认 Element UI 等依赖是否有 Vue3 版本
Vue3 兼容选项式 API
好消息:Vue3 仍然支持 Options API。如果团队短期不适应,可以先用 Options API,逐步过渡到 Composition API。
总结
Vue2 → Vue3 的核心变化:
| 维度 | Vue2 | Vue3 |
|---|---|---|
| 响应式 | defineProperty | Proxy |
| API 风格 | Options API | Composition API |
| 根节点 | 单一根 | Fragment 多根 |
| 构建 | Vue CLI | Vite |
| 状态 | Vuex | Pinia |
| 类型 | 弱 | TypeScript 重写 |
一句话总结:Vue3 用 Proxy 解决了 Vue2 的响应式缺陷,用 Composition API 解决了逻辑复用难题,整体更现代、更快、类型更友好。 新项目无脑选 Vue3。