Vue 3 与 Vue 2 核心区别
·
文章目录
前言
Vue 3 是一次从底层到上层的全面重写,理解 Vue 2 与 Vue 3 的核心区别对项目迁移和新项目选型至关重要。本篇会讲清楚:
- Composition API vs Options API
- 响应式系统重写(Proxy vs defineProperty)
- 模板语法变化
- 内置组件与 API 变更
- 移除的特性与迁移方案
一、响应式系统重写
1.1 Vue 2:Object.defineProperty
// Vue 2 的实现方式
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
// 依赖收集
track(target, key)
return val
},
set(newVal) {
if (newVal === val) return
val = newVal
// 派发更新
trigger(target, key)
}
})
}
// 问题:
// 1. 无法检测属性添加/删除(需要 Vue.set / Vue.delete)
// 2. 无法检测数组索引修改和长度修改
// 3. 需要递归遍历所有属性,初始化性能差
1.2 Vue 3:Proxy
// Vue 3 的实现方式
const handler = {
get(target, key, receiver) {
track(target, key)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
trigger(target, key)
return result
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
trigger(target, key)
return result
}
}
const state = new Proxy({ count: 0 }, handler)
// 优势:
// 1. 可以检测属性添加/删除
// 2. 可以检测数组索引修改和长度修改
// 3. 惰性代理,只在访问时才代理深层属性
1.3 对比
| 对比项 | Vue 2 | Vue 3 |
|---|---|---|
| API | Object.defineProperty |
Proxy |
| 属性添加/删除 | 无法检测,需要 Vue.set |
自动检测 |
| 数组索引修改 | 无法检测 | 自动检测 |
| 初始化性能 | 递归遍历所有属性 | 惰性代理,按需递归 |
| 兼容性 | IE11 支持 | IE11 不支持 |
二、Composition API vs Options API
2.1 Options API(Vue 2 风格)
export default {
data() {
return { count: 0, name: 'Alice' }
},
computed: {
double() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
},
mounted() {
console.log('mounted')
}
}
2.2 Composition API(Vue 3 风格)
<script setup>
import { ref, computed, onMounted } from 'vue'
const count = ref(0)
const name = ref('Alice')
const double = computed(() => count.value * 2)
const increment = () => count.value++
onMounted(() => {
console.log('mounted')
})
</script>
2.3 核心区别
| 对比项 | Options API | Composition API |
|---|---|---|
| 代码组织 | 按选项类型分组 | 按逻辑关注点分组 |
| 逻辑复用 | Mixins(有命名冲突) | Composables(来源清晰) |
| TypeScript | 类型推导困难 | 完整类型推导 |
this |
依赖 this 上下文 |
无 this,变量直接引用 |
| 可读性 | 简单组件直观 | 复杂组件更清晰 |
2.4 逻辑组织对比
// Options API:同一个功能的代码分散在不同选项中
export default {
data() {
return {
// 用户相关
userName: '',
// 主题相关
theme: 'light',
// 分页相关
page: 1,
pageSize: 10
}
},
methods: {
// 用户相关方法
fetchUser() { },
// 主题相关方法
toggleTheme() { },
// 分页相关方法
changePage() { }
}
}
// Composition API:同一功能的代码聚合在一起
import { useUser } from './composables/useUser'
import { useTheme } from './composables/useTheme'
import { usePagination } from './composables/usePagination'
const { userName, fetchUser } = useUser()
const { theme, toggleTheme } = useTheme()
const { page, pageSize, changePage } = usePagination()
三、模板语法变化
3.1 v-model 变更
<!-- Vue 2 -->
<!-- 默认 prop: value,事件: input -->
<MyInput v-model="text" />
<!-- 等价于 -->
<MyInput :value="text" @input="text = $event" />
<!-- Vue 3 -->
<!-- 默认 prop: modelValue,事件: update:modelValue -->
<MyInput v-model="text" />
<!-- 等价于 -->
<MyInput :modelValue="text" @update:modelValue="text = $event" />
<!-- Vue 3 支持多个 v-model -->
<MyForm v-model:name="name" v-model:age="age" />
3.2 v-if 与 v-for 优先级
<!-- Vue 2:v-for 优先级更高 -->
<!-- 以下代码在 Vue 2 中可以工作 -->
<li v-for="item in list" v-if="item.active">
{{ item.name }}
</li>
<!-- Vue 3:v-if 优先级更高 -->
<!-- 以下代码在 Vue 3 中 item 未定义! -->
<!-- 需要用 computed 先过滤 -->
3.3 移除 .native 修饰符
<!-- Vue 2:组件根元素的原生事件需要 .native -->
<MyButton @click.native="handleClick" />
<!-- Vue 3:自动透传,不再需要 .native -->
<MyButton @click="handleClick" />
四、移除的 API
4.1 事件总线 o n / on/ on/off/$once
// Vue 2:实例方法
const bus = new Vue()
bus.$on('message', handler)
bus.$emit('message', data)
bus.$off('message', handler)
// Vue 3:使用 mitt 库
import mitt from 'mitt'
const bus = mitt()
bus.on('message', handler)
bus.emit('message', data)
bus.off('message', handler)
4.2 过滤器 Filter
<!-- Vue 2 -->
{{ price | currency('$') }}
<!-- Vue 3:使用 computed 或函数替代 -->
{{ formatCurrency(price) }}
<script setup>
const formatCurrency = (value) => `$${value.toFixed(2)}`
</script>
4.3 $children
// Vue 2:访问子组件实例数组
this.$children[0].doSomething()
// Vue 3:使用 Template Refs
const childRef = ref(null)
childRef.value.doSomething()
4.4 $listeners
<!-- Vue 2:访问父组件传入的事件监听器 -->
<template>
<button v-on="$listeners">Click</button>
</template>
<!-- Vue 3:自动透传到根元素,或用 useAttrs() -->
<script setup>
const attrs = useAttrs()
</script>
五、新增特性
5.1 Fragment(多根节点)
<!-- Vue 2:必须有单一根元素 -->
<template>
<div>
<header>Header</header>
<main>Content</main>
</div>
</template>
<!-- Vue 3:支持多根节点 -->
<template>
<header>Header</header>
<main>Content</main>
<footer>Footer</footer>
</template>
5.2 Teleport
<!-- 将 DOM 传送到指定位置 -->
<Teleport to="body">
<div class="modal">弹窗内容</div>
</Teleport>
5.3 Suspense
<!-- 异步组件加载状态管理 -->
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
5.4 defineAsyncComponent
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => import('./MyComponent.vue'))
// 带配置
const AsyncComp = defineAsyncComponent({
loader: () => import('./MyComponent.vue'),
loadingComponent: LoadingSpinner,
errorComponent: ErrorDisplay,
delay: 200,
timeout: 30000
})
六、构建与工程化
6.1 Tree-shaking
// Vue 3 支持 Tree-shaking
// 只导入使用的功能,未使用的不会打包
import { ref, computed } from 'vue'
// watch、onMounted 等未使用就不会打包
// Vue 2 不支持 Tree-shaking
// 整个 Vue 运行时都会打包
6.2 Monorepo 架构
vue/
├── packages/
│ ├── reactivity/ # @vue/reactivity
│ ├── runtime-core/ # @vue/runtime-core
│ ├── runtime-dom/ # @vue/runtime-dom
│ ├── compiler-core/ # @vue/compiler-core
│ ├── compiler-dom/ # @vue/compiler-dom
│ └── vue/ # 完整包
七、迁移要点
7.1 常见迁移踩坑
| 问题 | Vue 2 | Vue 3 |
|---|---|---|
| 属性添加 | Vue.set(obj, key, val) |
obj[key] = val |
| 数组更新 | Vue.set(arr, index, val) |
arr[index] = val |
| 事件总线 | $on/$off/$emit |
mitt 库 |
| 过滤器 | {{ val | filter }} |
{{ filter(val) }} |
| v-model | value + input |
modelValue + update:modelValue |
| v-for/v-if | v-for 优先 | v-if 优先 |
7.2 Options API 与 Composition API 混用
<!-- 可以混用,但不推荐 -->
<script>
import { ref } from 'vue'
export default {
// Options API
data() {
return { count: 0 }
},
// Composition API
setup() {
const name = ref('Alice')
return { name }
}
}
</script>
<!-- 推荐:统一使用 <script setup> -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
const name = ref('Alice')
</script>
八、面试聚焦
8.1 迁移踩坑点
- 属性添加/删除:Vue 2 需要
Vue.set,Vue 3 直接操作即可 - 事件总线:Vue 3 移除
$on/$off,需要 mitt 库替代 - v-model:默认 prop 从
value改为modelValue - v-for/v-if 优先级:Vue 3 中 v-if 优先级更高
8.2 两种 API 混用问题
// Options API 和 Composition API 可以共存,但不推荐
// 原因:
// 1. 代码风格不统一
// 2. data 中的属性和 setup 中的 ref 会合并,容易混淆
// 3. 增加维护成本
九、易混淆点
- Options API 可以继续用:Vue 3 完全兼容 Options API,两种 API 可以在同一项目甚至同一组件中混用。
<script setup>不是新语法:它是setup()函数的编译时语法糖,自动暴露顶层变量。- Vue 3 不支持 IE11:因为 Proxy 无法 polyfill,需要 IE11 支持请使用 Vue 2。
- 事件透传:Vue 3 组件的事件会自动透传到根元素,不再需要
.native修饰符。
十、思考与练习
1. Vue 3 为什么用 Proxy 替代 Object.defineProperty?
解析:
- Proxy 可以检测属性添加/删除
- Proxy 可以检测数组索引修改和长度修改
- Proxy 支持惰性代理,性能更好
- Proxy 的缺点是不支持 IE11
2. Options API 和 Composition API 的核心区别是什么?
解析:
- 代码组织:Options API 按选项类型分组,Composition API 按逻辑关注点分组
- 逻辑复用:Options API 用 Mixins(有命名冲突),Composition API 用 Composables(来源清晰)
- TypeScript:Options API 类型推导困难,Composition API 完整类型推导
3. Vue 3 移除了哪些 API?如何替代?
解析:
$on/$off/$once→ mitt 库- 过滤器 → computed 或函数
$children→ Template Refs$listeners→ useAttrs() 或自动透传
4. Vue 3 的 v-model 有什么变化?
解析:
- 默认 prop 从
value改为modelValue - 事件从
input改为update:modelValue - 支持多个 v-model:
v-model:name、v-model:age
5. Vue 3 为什么不支持 IE11?
解析:Vue 3 使用 Proxy 实现响应式,Proxy 是 ES6 新增的原生 API,无法被 polyfill。IE11 不支持 Proxy,因此 Vue 3 无法在 IE11 上运行。
总结
- 响应式系统:Vue 2 用
Object.defineProperty,Vue 3 用Proxy - API 风格:Options API(按选项分组)vs Composition API(按逻辑分组)
- 模板变化:v-model 默认 prop 变更、v-for/v-if 优先级变化、移除 .native
- 移除特性: o n / on/ on/off、过滤器、 c h i l d r e n 、 children、 children、listeners
- 新增特性:Fragment、Teleport、Suspense、defineAsyncComponent
- 构建优化:Tree-shaking 支持、Monorepo 架构
更多推荐


所有评论(0)