Vue 2 到 Vue 3 的语法演进:从 Options API 到 Composition API 完全指南
前言
在前几篇博客中,我们从 Vue 项目的脚手架选型聊到 VSCode 配置,再到 Vue 的核心指令。今天,我们终于要触及 Vue 3 最核心、最颠覆性的变化——Composition API(组合式 API)。
如果你是一位 Vue 2 的老用户,初次接触 Composition API 时可能会有一种“这不像 Vue”的陌生感。如果你是一位刚入门的新手,面对 Options API 和 Composition API 两种写法,可能会陷入“我到底该学哪个”的困惑。
本文将带你系统理解从 Options API 到 Composition API 的演进逻辑,通过大量代码对比和真实项目案例,帮你建立起清晰的认知——不仅知道怎么写,更知道为什么这么写。
一、为什么要从 Options API 走向 Composition API?
1.1 Options API 是什么?
Options API 是 Vue 2 及 Vue 3 中依然支持的传统写法。它将组件的逻辑分散到不同的“选项”中——data、methods、computed、watch、mounted 等。
// Options API 写法 —— Vue 2 和 Vue 3 都支持
export default {
data() {
return {
count: 0,
user: { name: '', age: 0 }
}
},
computed: {
doubledCount() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
},
mounted() {
console.log('组件已挂载')
}
}
这种写法对初学者非常友好,结构清晰,一看就懂。在小型项目中,Options API 依然是一个不错的选择。
1.2 Options API 的痛点
然而,当项目变得复杂时,Options API 的局限性就会暴露出来。
痛点一:逻辑分散,难以维护
假设我们要实现一个用户信息表单,包含:加载用户数据、表单验证、提交保存、加载状态管理等。在 Options API 中,这些逻辑会被强制分散到不同的选项中:
export default {
data() {
return {
// 用户数据相关
user: { name: '', email: '' },
// 加载状态相关
loading: false,
// 验证错误相关
errors: {}
}
},
methods: {
// 加载用户数据的方法
fetchUser() { /* ... */ },
// 表单验证的方法
validateForm() { /* ... */ },
// 提交保存的方法
submitForm() { /* ... */ }
},
computed: {
// 与用户数据相关的计算
isFormValid() { /* ... */ }
},
mounted() {
// 加载用户数据
this.fetchUser()
}
}
同一个功能(比如“加载用户数据”)的代码散落在 data、methods、mounted 三个不同的地方。当组件有十几个功能时,你需要在不同选项之间来回跳转,代码的可读性和可维护性急剧下降。
痛点二:逻辑复用困难
在 Options API 中,逻辑复用主要靠 Mixins(混入) 。但 Mixins 存在几个致命问题:
- 命名冲突:两个 Mixin 定义了同名方法,后者会覆盖前者
- 来源不清晰:组件中使用的属性和方法不知道来自哪个 Mixin
- 隐式依赖:Mixin 之间可能相互依赖,难以追踪
1.3 Composition API 的解决方案
Composition API 的核心思想是:按功能组织代码,而不是按类型分类。
如果说 Options API 是把所有“写周报”需要的东西放一起、所有“画设计稿”需要的东西放一起,那么 Composition API 就是:做什么事,拿什么包,干净利落。
Composition API 通过 setup() 函数将相关的状态、方法、计算属性、生命周期钩子放在一起,使得代码组织更加灵活和清晰。
二、核心语法对比:从 Options API 到 Composition API
2.1 响应式数据定义
Options API(Vue 2 & Vue 3) :
export default {
data() {
return {
count: 0,
user: { name: '张三', age: 25 }
}
}
}
Composition API(Vue 3) :
import { ref, reactive } from 'vue'
export default {
setup() {
// 基本类型用 ref
const count = ref(0) // count 是一个 Ref 对象,通过 .value 访问
// 对象类型用 reactive
const user = reactive({ name: '张三', age: 25 })
return { count, user }
}
}
ref vs reactive 的使用建议:
| 场景 | 推荐 API | 原因 |
|---|---|---|
| 基本类型(number、string、boolean) | ref |
reactive 不支持基本类型 |
| 对象/数组(需要整体替换) | ref |
reactive 重新赋值会丢失响应式 |
| 复杂对象(表单、配置项) | reactive |
无需 .value,访问更自然 |
| 不确定用哪个 | ref |
更通用,适合任意类型 |
2.2 计算属性
Options API :
export default {
data() {
return { message: 'Hello, Vue!' }
},
computed: {
reversedMessage() {
return this.message.split('').reverse().join('')
}
}
}
Composition API :
import { ref, computed } from 'vue'
export default {
setup() {
const message = ref('Hello, Vue!')
const reversedMessage = computed(() => {
return message.value.split('').reverse().join('')
})
return { message, reversedMessage }
}
}
2.3 方法
Options API :
export default {
data() {
return { count: 0 }
},
methods: {
increment() {
this.count++
}
}
}
Composition API :
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
const increment = () => {
count.value++
}
return { count, increment }
}
}
注意:在 Composition API 中,方法就是普通的 JavaScript 函数,不再需要通过
methods选项包裹。
2.4 生命周期钩子
Vue 3 将生命周期钩子转换为可导入的函数形式:
| Vue 2 Options API | Vue 3 Composition API |
|---|---|
beforeCreate |
不需要(setup() 之前执行) |
created |
不需要(setup() 之前执行) |
beforeMount |
onBeforeMount |
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeDestroy |
onBeforeUnmount |
destroyed |
onUnmounted |
Options API :
export default {
data() {
return { count: 0 }
},
mounted() {
console.log('组件已挂载,count =', this.count)
},
beforeDestroy() {
console.log('组件即将销毁')
}
}
Composition API :
import { ref, onMounted, onBeforeUnmount } from 'vue'
export default {
setup() {
const count = ref(0)
onMounted(() => {
console.log('组件已挂载,count =', count.value)
})
onBeforeUnmount(() => {
console.log('组件即将销毁')
})
return { count }
}
}
2.5 侦听器(watch)
Options API :
export default {
data() {
return { searchText: '' }
},
watch: {
searchText(newVal, oldVal) {
console.log('搜索词变化:', oldVal, '->', newVal)
}
}
}
Composition API :
import { ref, watch } from 'vue'
export default {
setup() {
const searchText = ref('')
watch(searchText, (newVal, oldVal) => {
console.log('搜索词变化:', oldVal, '->', newVal)
})
return { searchText }
}
}
三、<script setup> 语法糖:更简洁的 Composition API
在 Vue 3.2 中,官方引入了 <script setup> 语法糖,让 Composition API 的写法更加简洁。
使用 setup() 函数的写法:
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
const increment = () => count.value++
onMounted(() => {
console.log('mounted')
})
return { count, increment } // 必须手动 return
}
}
</script>
使用 <script setup> 语法糖的写法:
<script setup>
import { ref, onMounted } from 'vue'
// 直接定义,无需 return
const count = ref(0)
const increment = () => count.value++
onMounted(() => {
console.log('mounted')
})
</script>
<script setup> 的优势:
- 代码更少:不需要
export default和return - 更自然的书写体验:顶层的变量和函数自动暴露给模板
- 更好的性能:编译时优化,避免了不必要的代理层和
this上下文处理 - 更好的 TypeScript 支持:类型推断更加精准
官方推荐:如果你使用单文件组件(SFC)和 Composition API,
<script setup>是官方推荐的语法。
四、真实项目实战:从 Options API 到 Composition API
4.1 场景一:用户信息表单(中型组件)
假设我们要实现一个用户信息编辑表单,包含:加载用户数据、表单验证、提交保存、加载状态管理。
Options API 实现(代码分散) :
<script>
export default {
data() {
return {
// ---- 用户数据 ----
user: { name: '', email: '', phone: '' },
// ---- 表单状态 ----
loading: false,
errors: {},
isSubmitting: false
}
},
computed: {
// ---- 表单验证 ----
isFormValid() {
return this.user.name.trim() &&
this.user.email.trim() &&
this.user.phone.trim()
}
},
methods: {
// ---- 加载数据 ----
async fetchUser() {
this.loading = true
try {
const res = await api.getUser(this.$route.params.id)
this.user = res.data
} finally {
this.loading = false
}
},
// ---- 表单验证 ----
validateField(field) {
if (!this.user[field]?.trim()) {
this.errors[field] = '此字段为必填项'
} else {
delete this.errors[field]
}
},
// ---- 提交保存 ----
async submitForm() {
this.isSubmitting = true
try {
await api.updateUser(this.user)
this.$message.success('保存成功')
} finally {
this.isSubmitting = false
}
}
},
mounted() {
this.fetchUser()
}
}
</script>
Composition API 实现(按功能分组) :
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import api from '@/api'
const route = useRoute()
// ====== 1. 用户数据管理 ======
const user = reactive({ name: '', email: '', phone: '' })
const loading = ref(false)
const fetchUser = async () => {
loading.value = true
try {
const res = await api.getUser(route.params.id)
Object.assign(user, res.data) // 保持响应式
} finally {
loading.value = false
}
}
// ====== 2. 表单验证 ======
const errors = reactive({})
const isFormValid = computed(() => {
return user.name.trim() && user.email.trim() && user.phone.trim()
})
const validateField = (field) => {
if (!user[field]?.trim()) {
errors[field] = '此字段为必填项'
} else {
delete errors[field]
}
}
// ====== 3. 提交保存 ======
const isSubmitting = ref(false)
const submitForm = async () => {
isSubmitting.value = true
try {
await api.updateUser(user)
ElMessage.success('保存成功')
} finally {
isSubmitting.value = false
}
}
// ====== 4. 初始化加载 ======
onMounted(() => {
fetchUser()
})
</script>
对比总结:Composition API 版本将代码按功能模块(用户数据管理、表单验证、提交保存)清晰分组,每个功能的相关代码集中在一起,一目了然。
4.2 场景二:逻辑复用 —— 封装组合式函数
这是 Composition API 最强大的能力之一——将可复用的逻辑封装成组合式函数(Composables) 。
场景:多个页面都需要实现列表数据的分页加载功能。
封装 usePagination 组合式函数:
// composables/usePagination.js
import { ref, reactive, computed } from 'vue'
export function usePagination(fetchFn) {
// 状态
const list = ref([])
const loading = ref(false)
const pagination = reactive({
page: 1,
pageSize: 20,
total: 0
})
// 计算属性
const hasMore = computed(() => {
return pagination.total > pagination.page * pagination.pageSize
})
// 方法
const loadData = async (reset = false) => {
if (reset) {
pagination.page = 1
list.value = []
}
loading.value = true
try {
const res = await fetchFn({
page: pagination.page,
pageSize: pagination.pageSize
})
list.value = reset ? res.items : [...list.value, ...res.items]
pagination.total = res.total
} finally {
loading.value = false
}
}
const loadMore = () => {
if (hasMore.value && !loading.value) {
pagination.page++
loadData()
}
}
// 返回给组件使用的 API
return { list, loading, pagination, hasMore, loadData, loadMore }
}
在组件中使用:
<script setup>
import { usePagination } from '@/composables/usePagination'
import { getOrderList } from '@/api/order'
// 只需一行代码,即可获得完整的分页能力
const { list, loading, loadData, loadMore } = usePagination(getOrderList)
// 组件挂载时自动加载第一页
loadData(true)
</script>
<template>
<div>
<div v-for="item in list" :key="item.id">{{ item.name }}</div>
<button @click="loadMore" :disabled="loading">加载更多</button>
</div>
</template>
组合式函数的命名规范:统一使用 use 前缀,如 usePagination、useTable、useForm 等。
对比 Mixins:组合式函数彻底解决了 Mixins 的命名冲突和来源不清晰的问题。每个组合式函数返回的对象都是显式的,你可以清楚地知道每个属性和方法来自哪里。
五、Vue 2 到 Vue 3 的其他语法变化
5.1 响应式系统:从 defineProperty 到 Proxy
这是 Vue 3 最底层的变革:
| 对比维度 | Vue 2 | Vue 3 |
|---|---|---|
| 实现方式 | Object.defineProperty |
Proxy |
| 新增属性 | 需要 Vue.set() |
直接添加即可触发更新 |
| 删除属性 | 需要 Vue.delete() |
直接 delete 即可触发更新 |
| 数组索引修改 | 无法检测 | ✅ 支持 |
| 性能 | 较慢 | 提升约 2 倍 |
5.2 全局 API 的变化
| Vue 2 | Vue 3 |
|---|---|
new Vue() |
createApp() |
Vue.component() |
app.component() |
Vue.directive() |
app.directive() |
Vue.mixin() |
app.mixin() |
Vue.use() |
app.use() |
5.3 v-model 的变化
| 对比 | Vue 2 | Vue 3 |
|---|---|---|
| 默认 prop | value |
modelValue |
| 默认事件 | input |
update:modelValue |
| 多个绑定 | 不支持 | ✅ 支持 v-model:name、v-model:email |
.sync 修饰符 |
✅ 支持 | ❌ 已移除(用 v-model:propName 替代) |
5.4 移除的 API
$on、$off、$once(实例事件方法被移除)filters过滤器(推荐用计算属性或方法替代)$children(推荐使用$refs或组件间通信).native修饰符(Vue 3 自动将事件监听添加到根元素)
六、如何选择:Options API vs Composition API?
6.1 选择 Options API 的场景
- 小型项目或简单组件:逻辑简单,Options API 足够
- 团队对 Vue 2 非常熟悉:迁移成本考虑
- 展示型组件:主要是展示数据,逻辑很轻
6.2 选择 Composition API 的场景
- 中大型项目:代码组织更清晰,可维护性更强
- 复杂组件:多个功能交织,需要按逻辑分组
- 需要逻辑复用:多个组件共享相同逻辑
- 使用 TypeScript:Composition API 的类型推断更精准
- 新项目:拥抱未来,Vue 3 的官方推荐方向
6.3 迁移策略
对于已有的 Vue 2 项目,不建议一次性全部重写:
- 新组件使用 Composition API:在新功能或新页面中率先使用
- 逐步迁移旧组件:在有重构需求时,将旧组件改为 Composition API
- 使用
@vue/compat迁移构建:在 Vue 3 上运行 Vue 2 代码,观察兼容警告并逐项修复
七、总结
从 Options API 到 Composition API 的演进,本质上是 Vue 从框架主导走向开发者主导的转变:
| 维度 | Options API(Vue 2) | Composition API(Vue 3) |
|---|---|---|
| 代码组织 | 按类型分类(data、methods、computed) | 按功能分组(相关逻辑放在一起) |
| 逻辑复用 | Mixins(命名冲突、来源不清晰) | 组合式函数(显式、可追踪) |
| TypeScript 支持 | 基础支持,this 类型推断困难 |
原生支持,类型推断精准 |
| 学习曲线 | 平缓,对初学者友好 | 陡峭,需要理解新的编程范式 |
| 适用场景 | 小型项目、简单组件 | 中大型项目、复杂组件 |
Composition API 不是要取代 Options API,而是为开发者提供了一个更强大、更灵活的选择。Vue 3 同时支持两种 API 风格,你可以在同一个项目中混合使用。
理解这段演进历程,不仅能帮你写出更好的 Vue 代码,更能让你在面对新技术时,保持一份“知其然,知其所以然”的从容。

浙公网安备 33010602011771号