Vue 3 API 完全指南:从 Options 到 Composition 的进阶之路
一份覆盖 Vue 3 全部核心 API 的深度文档------Composition API、响应式系统、生命周期、编译宏、内置组件、v-model 进化,附大量可直接复制的 Vue 2/3 对比代码。
目录
- [引言:Vue 3 带来了什么](#引言:Vue 3 带来了什么 "#%E5%BC%95%E8%A8%80")
- [Composition API vs Options API:范式之变](#Composition API vs Options API:范式之变 "#%E4%B8%80composition-api-vs-options-api%E8%8C%83%E5%BC%8F%E4%B9%8B%E5%8F%98")
- [响应式系统:从 defineProperty 到 Proxy](#响应式系统:从 defineProperty 到 Proxy "#%E4%BA%8C%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F%E4%BB%8E-defineproperty-%E5%88%B0-proxy")
- 生命周期钩子:新旧对照
- [script setup 编译宏详解](#script setup 编译宏详解 "#%E5%9B%9Bscript-setup-%E7%BC%96%E8%AF%91%E5%AE%8F%E8%AF%A6%E8%A7%A3")
- [新增内置组件:Teleport / Suspense / Fragment](#新增内置组件:Teleport / Suspense / Fragment "#%E4%BA%94%E6%96%B0%E5%A2%9E%E5%86%85%E7%BD%AE%E7%BB%84%E4%BB%B6teleport--suspense--fragment")
- [v-model 的进化](#v-model 的进化 "#%E5%85%ADv-model-%E7%9A%84%E8%BF%9B%E5%8C%96")
- [其他重要 API 变更](#其他重要 API 变更 "#%E4%B8%83%E5%85%B6%E4%BB%96%E9%87%8D%E8%A6%81-api-%E5%8F%98%E6%9B%B4")
- [迁移实战:从 Vue 2 平滑升级](#迁移实战:从 Vue 2 平滑升级 "#%E5%85%AB%E8%BF%81%E7%A7%BB%E5%AE%9E%E6%88%98%E4%BB%8E-vue-2-%E5%B9%B3%E6%BB%91%E5%8D%87%E7%BA%A7")
- 总结
引言:Vue 3 带来了什么
Vue 3 是 Vue.js 框架迄今为止最重要的一次重写。它不仅仅是 API 的增减,而是从底层响应式引擎到上层开发体验的全面革新。以下是 Vue 3 最核心的几大变化:
01 Composition API --- 全新的逻辑组织和复用模式,解决 Options API 在大型组件中代码分散、逻辑复用困难的问题。
02 Proxy 响应式 --- 用 Proxy 替代 Object.defineProperty,实现全属性监听,消除 Vue 2 的响应式盲区。
03 性能提升 --- 包体积减少约 40%,首屏渲染提升 30%+,更新性能提升约 40%,内存占用降低约 60%。
04 TypeScript 原生支持 --- Vue 3 源码用 TypeScript 重写,类型推断更完善,<script setup> 中无需额外类型声明。
Vue 2 与 Vue 3 性能对比(Vue 2 基准 = 100):
| 指标 | Vue 2 | Vue 3 | 提升幅度 |
|---|---|---|---|
| 包体积 (min+gzip) | 20KB | 12KB | -40% |
| 首屏渲染速度 | 100 | 130 | +30% |
| 更新性能 | 100 | 140 | +40% |
| 内存占用 | 100 | 60 | -40% |
一、Composition API vs Options API:范式之变
1.1 Options API 的痛点
Vue 2 的 Options API 通过 data、methods、computed、watch 等选项组织代码。在小型组件中很直观,但随着组件变大,同一功能的代码被拆散到不同选项中,阅读时需要反复跳转。
更关键的问题是逻辑复用。Vue 2 的 mixin 虽然能复用逻辑,但存在命名冲突、数据来源不清晰、类型推断弱等问题。
1.2 Composition API 的设计理念
Composition API 的核心思想是:按功能逻辑组织代码,而不是按选项类型。相关联的响应式数据、计算属性、方法和监听器放在一起,形成一个内聚的逻辑单元。
Vue 2 Options API:
javascript
export default {
data() {
return {
count: 0,
user: null,
}
},
computed: {
doubleCount() {
return this.count * 2
},
userName() {
return this.user?.name ?? '未登录'
}
},
methods: {
increment() {
this.count++
},
fetchUser() { /* ... */ }
},
watch: {
count(val) {
console.log('count changed', val)
}
}
}
Vue 3 Composition API:
javascript
import { ref, computed, watch } from 'vue'
// 计数器逻辑 ------ 内聚在一起
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
watch(count, (val) => {
console.log('count changed', val)
})
// 用户逻辑 ------ 独立单元
const user = ref(null)
const userName = computed(() =>
user.value?.name ?? '未登录'
)
function fetchUser() { /* ... */ }
可以看到,在 Composition API 中,计数器逻辑和用户逻辑各自独立,不再被拆散到 data、computed、methods、watch 中。如果需要复用计数器逻辑,直接提取成一个函数即可:
javascript
// composables/useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initial = 0) {
const count = ref(initial)
const double = computed(() => count.value * 2)
function increment() { count.value++ }
function decrement() { count.value-- }
function reset() { count.value = initial }
return { count, double, increment, decrement, reset }
}
// 在任意组件中使用
const { count, double, increment } = useCounter(10)
💡 Composables vs Mixins
Composition API 的逻辑复用方式叫做 Composables(组合式函数)。相比 Mixins,它有明确的来源、不会命名冲突、TypeScript 类型推断完美,是目前 Vue 官方推荐的逻辑复用方案。
1.3 <script setup> 语法糖
<script setup> 是 Vue 3.2 引入的编译期语法糖,它是目前编写组件的推荐方式。所有顶层变量和函数自动暴露给模板,无需 return。
标准 setup() 函数:
vue
<script>
import { ref } from 'vue'
export default {
setup() {
const msg = ref('Hello')
return { msg }
}
}
</script>
<script setup>:
vue
<script setup>
import { ref } from 'vue'
const msg = ref('Hello')
// 自动暴露给模板,无需 return
</script>
Options API 与 Composition API 多维度对比:
| 维度 | Options API (Vue 2) | Composition API (Vue 3) |
|---|---|---|
| 代码组织 | 6/10 | 9/10 |
| 逻辑复用 | 5/10 | 9.5/10 |
| TypeScript 支持 | 4/10 | 9.5/10 |
| Tree-shaking | 3/10 | 9/10 |
| 学习曲线(易上手度) | 9/10 | 6/10 |
| 打包体积 | 5/10 | 9/10 |
二、响应式系统:从 defineProperty 到 Proxy
2.1 底层原理对比
Vue 2 使用 Object.defineProperty 实现响应式,它有已知的三个盲区:
- 无法检测属性的新增和删除(需要
Vue.set/Vue.delete) - 无法检测数组通过索引修改元素(
arr[0] = newValue不触发更新) - 无法检测数组长度修改(
arr.length = 0不触发更新)
Vue 3 使用 Proxy 实现响应式,一举消除了以上所有盲区,且性能更好(初始化时不需要递归遍历所有属性)。
javascript
// Vue 3 响应式核心原理(简化版)
function reactive(target) {
return new Proxy(target, {
get(obj, key) {
track(obj, key) // 收集依赖
return Reflect.get(obj, key)
},
set(obj, key, value) {
const result = Reflect.set(obj, key, value)
trigger(obj, key) // 触发更新
return result
},
deleteProperty(obj, key) {
const result = Reflect.deleteProperty(obj, key)
trigger(obj, key)
return result
}
})
}
2.2 ref 与 reactive
Vue 3 提供了两种创建响应式数据的方式,适用场景不同:
| 特性 | ref() | reactive() |
|---|---|---|
| 支持类型 | 任意类型(原始值 + 对象) | 仅对象/数组 |
| 访问方式 | 需要 .value(模板中自动解包) |
直接访问属性 |
| 解构响应性 | 解构后仍保持响应 | 解构后失去响应(需 toRefs) |
| 整体替换 | 直接赋值 ref.value = newVal |
不能整体替换,只能改属性 |
| 推荐场景 | 原始值、需要替换整体的对象 | 表单、配置对象等结构性数据 |
javascript
import { ref, reactive } from 'vue'
// ref:适合原始值和需要整体替换的场景
const count = ref(0)
count.value++ // JS 中需要 .value
// <span>{{ count }}</span> 模板中自动解包,不需要 .value
const list = ref([1, 2, 3])
list.value = [4, 5, 6] // 整体替换,保持响应性
// reactive:适合对象/数组,直接操作属性
const form = reactive({
name: '',
age: 0,
hobbies: []
})
form.name = '张三' // 直接赋值,响应式
form.hobbies.push('coding') // 数组操作也响应式
⚠️ reactive 的解构陷阱
解构 reactive 对象会丢失响应性 。如果需要解构,必须使用
toRefs包装:
javascript
import { reactive, toRefs } from 'vue'
const state = reactive({
name: '张三',
age: 25
})
// ❌ 解构后失去响应性
const { name, age } = state
// ✅ 使用 toRefs 保持响应性
const { name, age } = toRefs(state)
2.3 computed 计算属性
Vue 3 的 computed 是一个函数,接收一个 getter 函数或一个带 get/set 的对象。
javascript
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
// 只读计算属性
const fullName = computed(() => `${firstName.value}${lastName.value}`)
// 可写计算属性
const fullName2 = computed({
get() {
return `${firstName.value}${lastName.value}`
},
set(newValue) {
// 注意:中文姓名拆分仅为示例
[firstName.value, lastName.value] = newValue.split(' ')
}
})
2.4 watch 与 watchEffect
| 特性 | watch | watchEffect |
|---|---|---|
| 监听方式 | 显式指定监听源 | 自动收集依赖 |
| 初始执行 | 不执行(可设 immediate: true) | 立即执行一次 |
| 获取旧值 | 可以 | 不可以 |
| 适用场景 | 需要在值变化时执行副作用,且需要旧值 | 只需根据依赖执行副作用,不关心旧值 |
javascript
import { ref, watch, watchEffect } from 'vue'
const keyword = ref('')
const page = ref(1)
// watch:显式监听,可获取新旧值
watch(keyword, (newVal, oldVal) => {
console.log(`关键词从 ${oldVal} 变为 ${newVal}`)
search(newVal)
})
// 监听多个源
watch([keyword, page], ([newKeyword, newPage], [oldKeyword, oldPage]) => {
fetchList(newKeyword, newPage)
})
// watchEffect:自动收集依赖,立即执行
watchEffect(() => {
// 自动检测到 keyword 和 page 的使用
fetchList(keyword.value, page.value)
})
// watch 高级选项
watch(keyword, doSearch, {
immediate: true, // 立即执行一次
flush: 'post', // 在 DOM 更新后执行
once: true // 只执行一次(Vue 3.4+)
})
💡 监听 reactive 对象的注意事项
监听
reactive对象的某个属性时,需要用 getter 函数:watch(() => state.name, callback)。直接写watch(state.name, ...)不会工作,因为传递的是值而非响应式引用。
2.5 toRef / toRefs / toRaw
javascript
import { reactive, toRef, toRefs, toRaw } from 'vue'
const state = reactive({
name: '张三',
age: 25
})
// toRef:将对象的某个属性转为 ref,保持双向响应
const nameRef = toRef(state, 'name')
nameRef.value = '李四' // state.name 也变为 '李四'
// toRefs:将对象所有属性转为 ref,常用于解构
const { name, age } = toRefs(state)
// toRaw:获取响应式对象的原始对象(脱离响应式)
const raw = toRaw(state)
console.log(raw === state) // false,raw 是原始对象
2.6 shallowRef / shallowReactive
当处理大型数据结构时,深层响应式会带来性能开销。Vue 3 提供浅层响应式 API:
javascript
import { shallowRef, shallowReactive, triggerRef } from 'vue'
// shallowRef:只有 .value 替换才触发更新
const bigList = shallowRef([])
bigList.value.push('item') // ❌ 不会触发更新
bigList.value = [...bigList.value, 'item'] // ✅ 触发更新
// 手动触发浅层 ref 的更新
bigList.value.push('item2')
triggerRef(bigList) // 手动通知更新
// shallowReactive:只有根级属性是响应式的
const obj = shallowReactive({
info: { name: '张三' }
})
obj.info = { name: '李四' } // ✅ 触发更新(根级)
obj.info.name = '王五' // ❌ 不触发更新(深层)
三、生命周期钩子:新旧对照
Vue 3 将生命周期钩子改为了函数形式,名称也有调整。主要变化:beforeDestroy → onBeforeUnmount,destroyed → onUnmounted,beforeCreate 和 created 被 setup() 本身替代。
| Vue 2 | Vue 3 (Composition API) | 说明 |
|---|---|---|
| beforeCreate | setup() 开头 | 组件实例创建前,setup 替代 |
| created | setup() 中 | 组件实例创建后,setup 替代 |
| beforeMount | onBeforeMount | DOM 挂载前 |
| mounted | onMounted | DOM 挂载后 |
| beforeUpdate | onBeforeUpdate | 数据更新前 |
| updated | onUpdated | 数据更新后 |
| beforeDestroy | onBeforeUnmount | 组件卸载前(重命名) |
| destroyed | onUnmounted | 组件卸载后(重命名) |
| --- | onErrorCaptured | 捕获后代组件错误(新增) |
| --- | onActivated | keep-alive 激活时 |
| --- | onDeactivated | keep-alive 停用时 |
| --- | onServerPrefetch | SSR 专属(新增) |
vue
<script setup>
import {
onBeforeMount, onMounted,
onBeforeUpdate, onUpdated,
onBeforeUnmount, onUnmounted,
onActivated, onDeactivated,
onErrorCaptured
} from 'vue'
// setup() 本身等同于 beforeCreate + created
console.log('setup 执行 = beforeCreate + created')
onBeforeMount(() => {
console.log('DOM 即将挂载')
})
onMounted(() => {
console.log('DOM 已挂载,可以操作 DOM')
})
onBeforeUpdate(() => {
console.log('数据即将更新')
})
onUpdated(() => {
console.log('数据已更新,DOM 已重新渲染')
})
onBeforeUnmount(() => {
console.log('组件即将卸载,清理定时器等')
})
onUnmounted(() => {
console.log('组件已卸载')
})
// keep-alive 相关
onActivated(() => console.log('从缓存激活'))
onDeactivated(() => console.log('移入缓存'))
// 错误捕获
onErrorCaptured((err, instance, info) => {
console.error('子组件错误:', err)
return false // 阻止错误继续向上传播
})
</script>
四、<script setup> 编译宏详解
在 <script setup> 中,Vue 提供了几个编译宏(编译期处理,不需要 import)来定义组件的 props、emits 等。
4.1 defineProps
vue
<script setup>
// 运行时声明
const props = defineProps({
title: String,
count: {
type: Number,
default: 0,
required: false,
validator: (val) => val >= 0
}
})
// TypeScript 类型声明(推荐)
const props = defineProps<{
title: string
count?: number
items?: string[]
}>()
// 带默认值的 TS 声明(Vue 3.5+ 使用 withDefaults)
const props = withDefaults(defineProps<{
title: string
count?: number
items?: string[]
}>(), {
count: 0,
items: () => ['默认项']
})
</script>
4.2 defineEmits
vue
<script setup>
// 运行时声明
const emit = defineEmits(['change', 'submit'])
emit('change', newValue)
// TypeScript 类型声明(推荐)
const emit = defineEmits<{
(e: 'change', value: string): void
(e: 'submit', data: { name: string; age: number }): void
}>()
emit('submit', { name: '张三', age: 25 })
// Vue 3.3+ 更简洁的语法
const emit = defineEmits<{
change: [value: string]
submit: [data: { name: string; age: number }]
}>()
</script>
4.3 defineExpose
使用 <script setup> 的组件默认是封闭的 ------父组件通过 ref 无法访问子组件的内部变量。需要用 defineExpose 显式暴露。
vue
<!-- 子组件 Child.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
const reset = () => { count.value = 0 }
// 暴露给父组件
defineExpose({ count, reset })
</script>
vue
<!-- 父组件 -->
<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'
const childRef = ref()
onMounted(() => {
console.log(childRef.value.count) // 0
childRef.value.reset() // 调用子组件方法
})
</script>
<template>
<Child ref="childRef" />
</template>
4.4 useSlots 与 useAttrs
vue
<script setup>
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
// 判断是否传入了具名插槽
console.log(slots.header ? '有 header 插槽' : '无 header 插槽')
// 获取透传的属性(class、id、data-* 等)
console.log(attrs.class, attrs.id)
</script>
五、新增内置组件:Teleport / Suspense / Fragment
5.1 Teleport------传送门
Teleport 可以将组件的 DOM 渲染到指定位置,常见于弹窗、通知、下拉菜单等需要脱离当前 DOM 层级的场景。
vue
<template>
<button @click="showModal = true">打开弹窗</button>
<!-- 将弹窗传送到 body 下,避免被父级 overflow/z-index 影响 -->
<Teleport to="body">
<div v-if="showModal" class="modal">
<h2>弹窗标题</h2>
<p>弹窗内容</p>
<button @click="showModal = false">关闭</button>
</div>
</Teleport>
</template>
<script setup>
import { ref } from 'vue'
const showModal = ref(false)
</script>
💡 Teleport 的 to 属性
to接受 CSS 选择器字符串或 DOM 元素。如果目标元素不存在,会抛出警告。也可以用:disabled="true"禁用传送,让内容回到原位。
5.2 Suspense------异步组件加载
Suspense 用于协调异步组件的加载状态,在异步组件就绪前显示 fallback 内容。
vue
<template>
<Suspense>
<!-- 异步组件 -->
<template #default>
<AsyncDashboard />
</template>
<!-- 加载中状态 -->
<template #fallback>
<div class="loading">加载中...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
// 异步加载组件
const AsyncDashboard = defineAsyncComponent(() =>
import('./Dashboard.vue')
)
</script>
Suspense 也可以配合异步 setup() 使用:
vue
<!-- AsyncDashboard.vue ------ 异步 setup -->
<script setup>
import { ref } from 'vue'
// 顶层 await,组件变成异步组件
const data = await fetch('/api/dashboard').then(r => r.json())
const stats = ref(data)
</script>
⚠️ Suspense 仍是实验性功能
截至Vue 3 最新版本,Suspense 的 API 已趋于稳定但官方仍标记为实验性。生产环境使用时需注意错误处理------可以用
onErrorCaptured捕获异步组件的加载错误。
5.3 Fragment------多根节点
Vue 2 要求模板必须有且仅有一个根节点。Vue 3 移除了这个限制,组件可以有多个根节点(Fragment)。
Vue 2:必须单根
vue
<template>
<div class="wrapper">
<header>...</header>
<main>...</main>
<footer>...</footer>
</div>
</template>
Vue 3:多根节点
vue
<template>
<header>...</header>
<main>...</main>
<footer>...</footer>
</template>
六、v-model 的进化
Vue 3 对 v-model 做了重要调整,核心变化是支持多个 v-model 绑定,并且统一了 prop 和 event 的命名。
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 默认 prop 名 | value | modelValue |
| 默认 event 名 | input | update:modelValue |
| 多个 v-model | 不支持(需用 .sync) | 原生支持 |
| .sync 修饰符 | 支持 | 移除(被多 v-model 替代) |
| 自定义修饰符 | 不支持 | 支持 |
6.1 基本用法
Vue 2:
html
<!-- 父组件 -->
<Child v-model="text" />
<!-- 等同于 -->
<Child
:value="text"
@input="text = $event"
/>
<!-- 子组件 -->
<script>
export default {
props: ['value'],
methods: {
update(val) {
this.$emit('input', val)
}
}
}
</script>
Vue 3:
vue
<!-- 父组件 -->
<Child v-model="text" />
<!-- 等同于 -->
<Child
:modelValue="text"
@update:modelValue="text = $event"
/>
<!-- 子组件 -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
function update(val) {
emit('update:modelValue', val)
}
</script>
6.2 多个 v-model 绑定
这是 Vue 3 最大的改进之一------一个组件可以绑定多个 v-model:
vue
<!-- 父组件:同时绑定 firstName 和 lastName -->
<UserForm
v-model:firstName="form.firstName"
v-model:lastName="form.lastName"
/>
<!-- 子组件 UserForm.vue -->
<script setup>
const props = defineProps({
firstName: String,
lastName: String
})
const emit = defineEmits(['update:firstName', 'update:lastName'])
</script>
<template>
<input
:value="firstName"
@input="emit('update:firstName', $event.target.value)"
/>
<input
:value="lastName"
@input="emit('update:lastName', $event.target.value)"
/>
</template>
6.3 自定义修饰符
vue
<!-- 父组件:使用 trim 修饰符 -->
<MyInput v-model.trim="text" />
<!-- 子组件 MyInput.vue -->
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
function handleInput(e) {
let value = e.target.value
if (props.modelModifiers.trim) {
value = value.trim()
}
emit('update:modelValue', value)
}
</script>
七、其他重要 API 变更
7.1 createApp 替代 new Vue
Vue 2:
javascript
import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h => h(App)
}).$mount('#app')
// 全局注册
Vue.component('MyComp', MyComp)
Vue.directive('focus', { ... })
Vue.use(MyPlugin)
Vue 3:
javascript
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
// 应用级注册(不影响其他 app)
app.component('MyComp', MyComp)
app.directive('focus', { ... })
app.use(MyPlugin)
📌 为什么用 createApp
Vue 2 的全局 API(
Vue.component、Vue.use等)会污染全局状态,导致多个 Vue 应用互相影响。Vue 3 的createApp返回独立的应用实例,每个实例有自己的配置,真正做到应用隔离。
7.2 provide / inject
Vue 3 的 provide/inject 升级为函数形式,且支持 TypeScript 类型安全。
vue
<!-- 父组件 -->
<script setup>
import { provide, ref, readonly } from 'vue'
const theme = ref('dark')
const setTheme = (val) => { theme.value = val }
// 提供只读数据 + 修改方法,防止子组件直接修改
provide('theme', readonly(theme))
provide('setTheme', setTheme)
</script>
vue
<!-- 子/孙组件 -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme', 'light') // 第二个参数是默认值
const setTheme = inject('setTheme')
// 使用 InjectionKey 实现类型安全(TS 项目推荐)
import type { InjectionKey, Ref } from 'vue'
const themeKey: InjectionKey<Ref<string>> = Symbol('theme')
const theme = inject(themeKey) // 类型自动推断为 Ref<string> | undefined
</script>
7.3 移除的特性一览
| 移除的特性 | Vue 2 用法 | Vue 3 替代方案 |
|---|---|---|
| 过滤器 filter | `{{ price | formatPrice }}` |
| 事件总线 | new Vue() 作为 EventBus |
使用 mitt 等第三方库 |
| .sync 修饰符 | :title.sync="text" |
v-model:title="text" |
| on/off / $once | this.$on('event', fn) |
使用 mitt / provide+inject |
| $children | this.$children[0] |
使用 $refs 或 template refs |
| $listeners | v-on="$listeners" |
合并到 $attrs 中 |
| $scopedSlots | this.$scopedSlots.default |
统一为 $slots |
| 函数式组件语法 | functional: true |
普通函数组件,移除 functional 选项 |
7.4 自定义指令变化
javascript
// Vue 2 指令钩子
export default {
bind(el, binding) { }, // 指令第一次绑定到元素
inserted(el, binding) { }, // 元素插入父节点
update(el, binding) { }, // 所在组件 VNode 更新
componentUpdated() { },
unbind(el, binding) { } // 解绑
}
// Vue 3 指令钩子(与组件生命周期对齐)
export default {
created(el, binding) { }, // 元素属性和事件监听设置之前
beforeMount(el, binding) { }, // 挂载前
mounted(el, binding) { }, // 挂载后
beforeUpdate(el, binding) { }, // 更新前
updated(el, binding) { }, // 更新后
beforeUnmount(el, binding) { }, // 卸载前
unmounted(el, binding) { } // 卸载后
}
八、迁移实战:从 Vue 2 平滑升级
从 Vue 2 升级到 Vue 3 不是简单替换版本号。以下是推荐的迁移路径:
- 升级到 Vue 2.7 :Vue 2.7 已经内置了 Composition API、
<script setup>等核心特性,可以在 Vue 2 项目中提前使用新 API,降低迁移成本 - 移除废弃特性 :用迁移构建版本(
@vue/compat)运行项目,它会警告所有不兼容的用法 - 替换第三方库:Vuex → Pinia、Vue Router 3 → 4、Element UI → Element Plus 等
- 逐步重写组件:不必一次性全部改成 Composition API,Options API 在 Vue 3 中仍然支持
- 升级到 Vue 3 :移除
@vue/compat,正式切换到 Vue 3
💡 迁移建议
不要为了迁移而迁移。如果你的 Vue 2 项目运行良好且没有维护压力,可以继续使用。但如果要启动新项目,毫不犹豫地选择 Vue 3 +
<script setup>+ TypeScript + Pinia,这是目前的最佳实践技术栈。
生态库对应关系:
| 功能 | Vue 2 生态 | Vue 3 生态 |
|---|---|---|
| 路由 | Vue Router 3 | Vue Router 4 |
| 状态管理 | Vuex 3 / Vuex 4 | Pinia(官方推荐) |
| 构建工具 | Vue CLI | Vite(官方推荐) |
| UI 组件库 | Element UI / Ant Design Vue 1.x | Element Plus / Ant Design Vue 3+ / Naive UI |
| DevTools | Vue DevTools (beta) | Vue DevTools 6+ |
| SSR | Nuxt 2 | Nuxt 3 |
总结
Vue 3 不是 Vue 2 的小版本升级,而是一次从底层到开发范式的全面革新 。Composition API 解决了逻辑组织和复用的痛点,Proxy 响应式消除了所有盲区,<script setup> 让代码更简洁,Teleport / Suspense / 多 v-model 等新特性大幅扩展了表达能力。
迁移到 Vue 3 意味着拥抱更好的 TypeScript 支持、更小的包体积、更快的渲染性能和更灵活的代码组织方式。虽然学习曲线略有上升,但一旦掌握 Composition API 的思维模式,你会发现开发体验有了质的飞跃。
📌 核心要点回顾
- Composition API 按功能逻辑组织代码,用 Composables 替代 Mixins
- ref 用于原始值和需整体替换的对象,reactive 用于结构性数据
- watch 获取新旧值,watchEffect 自动收集依赖
<script setup>+ 编译宏是编写组件的推荐方式- Teleport 解决 DOM 层级问题,Suspense 处理异步加载
- 多 v-model 让双向绑定更灵活
- createApp 实现应用隔离,全局 API 不再污染
如果这篇文章对你有帮助,欢迎点赞收藏。有任何问题或补充,欢迎在评论区交流。