Vue.js 3 Composition API 完全指南:2026 版
Vue.js 3 引入了一种革命性的组件编写方式:Composition API。在 2026 年,随着 Vue 2 达到生命周期终点,掌握 Composition API 不再是可选项 —— 它是现代 Vue 开发的必备技能。
本指南涵盖 Vue 3 Composition API 的所有知识 —— 从基本概念到高级模式和最佳实践。
Vue 3 vs Vue 2 - 有什么变化?
在深入 Composition API 之前,让我们了解为什么 Vue 3 是游戏规则改变者。
Vue 2(Options API):
export default {
data() {
return {
count: 0,
user: null
}
},
methods: {
increment() {
this.count++
}
},
computed: {
doubleCount() {
return this.count * 2
}
}
}
CodeVue 3(Composition API):
import { ref, computed } from 'vue'
export default {
setup() {
const count = ref(0)
const user = ref(null)
const increment = () => {
count.value++
}
const doubleCount = computed(() => count.value * 2)
return { count, user, increment, doubleCount }
}
}
Code关键差异:
| Vue 2 Options API | Vue 3 Composition API |
|---|---|
按选项组织(data、methods、computed) |
按逻辑关注点组织 |
this 上下文可能令人困惑 |
没有 this —— 只有导入 |
| 跨组件重用逻辑困难 | 使用组合式函数轻松重用 |
| TypeScript 支持有限 | 一流的 TypeScript 支持 |
Composition API vs Options API
何时使用 Composition API
✅ 适用于: - 大型应用 - 逻辑复杂的组件 - 重视代码可重用性的团队 - TypeScript 项目 - 需要在组件间共享的逻辑
✅ Options API 仍然适用于: - 简单组件 - 小型项目 - 熟悉 Vue 2 的团队 - 快速原型
代码组织对比
Options API(按选项类型):
// UserComponent.vue
export default {
data() {
return {
// 用户数据
userName: '',
userEmail: '',
// 帖子数据
posts: [],
loading: false
}
},
methods: {
// 用户方法
updateName() { },
updateEmail() { },
// 帖子方法
fetchPosts() { },
deletePost() { }
}
}
CodeComposition API(按逻辑关注点):
// UserComponent.vue
import { useUser } from './composables/useUser'
import { usePosts } from './composables/usePosts'
export default {
setup() {
const { userName, userEmail, updateName, updateEmail } = useUser()
const { posts, loading, fetchPosts, deletePost } = usePosts()
return { userName, userEmail, posts, loading, updateName, deletePost }
}
}
Code好处: 相关逻辑保持在一起,使代码更易于理解和维护。
Setup 脚本语法(<script setup>)
Vue 3.2 引入了 <script setup> —— 一种编译时语法糖,使 Composition API 更简洁。
基本用法
<script setup>
import { ref, computed } from 'vue'
// 响应式状态
const count = ref(0)
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 方法
const increment = () => {
count.value++
}
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ doubleCount }}</p>
<button @click="increment">Add</button>
</div>
</template>
Code<script setup> 的好处:
- ✅ 更少的样板代码(无需 setup() 函数)
- ✅ 自动导入 setup 绑定
- ✅ 更好的 TypeScript 推断
- ✅ 更高效的运行时性能
使用 TypeScript
<script setup lang="ts">
import { ref } from 'vue'
// 类型化的 ref
const count = ref<number>(0)
// 类型化的事件
const handleClick = (event: MouseEvent) => {
console.log(event.clientX)
}
// 类型化的 props
interface Props {
title: string
count?: number
}
const props = withDefaults(defineProps<Props>(), {
count: 0
})
// 类型化的 emits
const emit = defineEmits<{
(e: 'update', value: number): void
}>()
</script>
Code响应式状态:ref() 和 reactive()
Vue 3 提供两种主要的响应式状态创建方式。
ref() - 用于原始值
import { ref } from 'vue'
const count = ref(0)
const name = ref('John')
const isActive = ref(true)
// 通过 .value 访问
count.value++
name.value = 'Jane'
Coderef() 适用于:
- 数字、字符串、布尔值
- 对象(当你想替换整个对象时)
- 数组
reactive() - 用于对象
import { reactive } from 'vue'
const state = reactive({
count: 0,
name: 'John',
isActive: true
})
// 无需 .value
state.count++
state.name = 'Jane'
Codereactive() 适用于:
- 具有多个属性的复杂对象
- 当你想组合相关状态时
ref() vs reactive() 对比
| 特性 | ref() |
reactive() |
|---|---|---|
| 原始值 | ✅ 是 | ❌ 否 |
| 对象 | ✅ 是 | ✅ 是 |
| 访问 | .value |
直接 |
| 替换整个对象 | ✅ 是 | ❌ 否(失去响应性) |
| TypeScript 推断 | 更好 | 良好 |
建议: 一致使用 ref() 以获得更简单的心智模型。
计算属性和侦听器
计算属性
计算属性根据其依赖进行缓存。
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// 计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 默认只读
console.log(fullName.value) // "John Doe"
// 使其可写
const editableFullName = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (value) => {
[firstName.value, lastName.value] = value.split(' ')
}
})
Code侦听器
侦听器在依赖变化时执行副作用。
import { ref, watch } from 'vue'
const searchQuery = ref('')
// 侦听单个 ref
watch(searchQuery, (newVal, oldVal) => {
console.log(`搜索从 ${oldVal} 变为 ${newVal}`)
// 执行 API 搜索
})
// 侦听多个源
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log('姓名已更改')
})
// 带选项的侦听
watch(searchQuery, async (newVal) => {
const results = await fetchResults(newVal)
}, {
debounce: 300, // 在最后一次更改后等待 300ms
immediate: true // 挂载时立即运行
})
Codewatch() vs watchEffect()
import { ref, watch, watchEffect } from 'vue'
const userId = ref(1)
const user = ref(null)
// watch - 显式依赖
watch(userId, async (newId) => {
user.value = await fetchUser(newId)
})
// watchEffect - 自动依赖跟踪
watchEffect(async () => {
user.value = await fetchUser(userId.value)
})
Code使用 watch() 当:
- 你需要旧值和新值
- 你想侦听特定源
- 你需要控制效果何时运行
使用 watchEffect() 当:
- 你只需要当前值
- 你想要自动依赖跟踪
- 你希望效果立即运行
组合式函数(可重用逻辑)
组合式函数是 Composition API 的超能力 —— 封装和返回响应式状态的可重用函数。
创建组合式函数
// composables/useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const doubleCount = computed(() => count.value * 2)
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
const reset = () => {
count.value = initialValue
}
return { count, doubleCount, increment, decrement, reset }
}
Code使用组合式函数
<script setup>
import { useCounter } from './composables/useCounter'
const { count, doubleCount, increment, reset } = useCounter(10)
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ doubleCount }}</p>
<button @click="increment">+</button>
<button @click="reset">Reset</button>
</div>
</template>
Code实际示例:useFetch
// composables/useFetch.js
import { ref } from 'vue'
export function useFetch(url) {
const data = ref(null)
const loading = ref(true)
const error = ref(null)
const fetchData = async () => {
loading.value = true
error.value = null
try {
const response = await fetch(url)
if (!response.ok) throw new Error('网络响应不正常')
data.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 挂载时获取
fetchData()
return { data, loading, error, refetch: fetchData }
}
Code用法:
<script setup>
import { useFetch } from './composables/useFetch'
const { data: users, loading, error, refetch } = useFetch('/api/users')
</script>
<template>
<div>
<div v-if="loading">加载中...</div>
<div v-else-if="error">错误:{{ error }}</div>
<div v-else>
<ul>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
<button @click="refetch">刷新</button>
</div>
</div>
</template>
CodeComposition API 中的生命周期钩子
Vue 3 生命周期钩子直接从 Options API 映射到 Composition API。
| Options API | Composition API | 何时 |
|---|---|---|
beforeCreate |
setup() |
组件初始化 |
created |
setup() |
响应式数据后 |
beforeMount |
onBeforeMount() |
DOM 插入前 |
mounted |
onMounted() |
DOM 插入后 |
beforeUpdate |
onBeforeUpdate() |
重新渲染前 |
updated |
onUpdated() |
重新渲染后 |
beforeDestroy |
onBeforeUnmount() |
卸载前 |
destroyed |
onUnmounted() |
卸载后 |
示例
import {
ref,
onMounted,
onUpdated,
onBeforeUnmount
} from 'vue'
export default {
setup() {
const element = ref(null)
onMounted(() => {
console.log('组件已挂载')
// 安全访问 DOM
console.log(element.value)
})
onUpdated(() => {
console.log('组件已更新')
})
onBeforeUnmount(() => {
console.log('组件将卸载')
// 清理:移除事件监听器、取消计时器
})
return { element }
}
}
CodeTypeScript 集成
Vue 3 具有一流的 TypeScript 支持,Composition API 与之配合完美。
类型化的 Props
<script setup lang="ts">
interface Props {
title: string
count?: number
isActive: boolean
}
// 带默认值
const props = withDefaults(defineProps<Props>(), {
count: 0,
isActive: true
})
</script>
Code类型化的 Emits
<script setup lang="ts">
// 定义 emit 类型
const emit = defineEmits<{
(e: 'update', value: number): void
(e: 'close'): void
(e: 'change', name: string, value: any): void
}>()
// 用法
emit('update', 42)
emit('close')
emit('change', 'name', 'John')
</script>
Code类型化的组合式函数
// composables/useUser.ts
import { ref } from 'vue'
interface User {
id: number
name: string
email: string
}
export function useUser() {
const user = ref<User | null>(null)
const loading = ref(false)
const error = ref<string | null>(null)
const fetchUser = async (id: number) => {
loading.value = true
try {
const response = await fetch(`/api/users/${id}`)
user.value = await response.json()
} catch (err) {
error.value = (err as Error).message
} finally {
loading.value = false
}
}
return { user, loading, error, fetchUser }
}
Code从 Options API 迁移指南
逐步迁移
1. 从 <script setup> 开始
<!-- 之前 -->
<script>
export default {
data() { return { count: 0 } },
methods: { increment() { this.count++ } }
}
</script>
<!-- 之后 -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => count.value++
</script>
Code2. 转换计算属性
// 之前
computed: {
doubleCount() {
return this.count * 2
}
}
// 之后
const doubleCount = computed(() => count.value * 2)
Code3. 转换侦听器
// 之前
watch: {
searchQuery(newVal) {
this.fetchResults(newVal)
}
}
// 之后
watch(searchQuery, (newVal) => {
fetchResults(newVal)
})
Code4. 将逻辑提取到组合式函数中
// composables/useSearch.js
export function useSearch(fetchFn) {
const query = ref('')
const results = ref([])
const loading = ref(false)
const search = async () => {
loading.value = true
results.value = await fetchFn(query.value)
loading.value = false
}
return { query, results, loading, search }
}
Code常见陷阱
❌ 忘记 ref 的 .value:
const count = ref(0)
console.log(count) // 错误!
console.log(count.value) // 正确
Code❌ 解构响应式对象:
const state = reactive({ count: 0 })
const { count } = state // 失去响应性!
// 使用 toRefs
import { toRefs } from 'vue'
const { count } = toRefs(state) // 保持响应性
Code❌ 在 Options API 组件中使用 Composition API:
// 不要混用!
export default {
data() { return { count: 0 } },
setup() {
// 这可以工作但令人困惑
}
}
Code最佳实践和常见模式
1. 默认使用 <script setup>
<script setup>
// 简洁、清晰、高效
import { ref } from 'vue'
const count = ref(0)
</script>
Code2. 使用 use 前缀命名组合式函数
// 好
useCounter()
useFetch()
useUser()
// 不好
getCounter()
fetchData()
userLogic()
Code3. 保持组合式函数小巧专注
// 好 - 单一职责
export function useCounter() { }
export function useFetch() { }
export function useAuth() { }
// 不好 - 太多关注点
export function useEverything() {
// 计数器逻辑
// 获取逻辑
// 认证逻辑
}
Code4. 从组合式函数返回显式对象
// 好 - 清晰的 API
export function useUser() {
// ...
return {
user,
loading,
error,
fetchUser,
updateUser
}
}
// 不好 - 不清楚返回什么
export function useUser() {
// ...
return { user, loading, error, fetchUser, updateUser, ...其他东西 }
}
Code5. 对公共 API 使用 readonly()
import { ref, readonly } from 'vue'
export function useCounter() {
const count = ref(0)
const increment = () => count.value++
// 暴露只读的 count
return {
count: readonly(count),
increment
}
}
Code6. 一致地处理异步状态
// 标准模式
export function useFetch(url) {
const data = ref(null)
const loading = ref(true)
const error = ref(null)
// 获取逻辑...
return { data, loading, error }
}
Code7. 记录组合式函数选项
/**
* @param {string} url - API 端点
* @param {Object} options - 获取选项
* @param {boolean} options.immediate - 挂载时获取
* @returns {Object} data, loading, error, refetch
*/
export function useFetch(url, options = {}) {
// ...
}
Code结论
Vue 3 Composition API 代表了我们编写 Vue 组件方式的根本性转变。它提供:
- ✅ 通过逻辑关注点更好地组织代码
- ✅ 使用组合式函数更容易地重用逻辑
- ✅ 一流的 TypeScript 支持
- ✅ 更灵活的组件模式
关键要点:
1. 默认使用 <script setup> 以获得更简洁的语法
2. 使用组合式函数组织代码
3. 一致使用 ref() 处理响应式状态
4. 拥抱 TypeScript 以获得更好的开发体验
5. 遵循命名约定(use 前缀)
下一步: - 将一个组件重构为 Composition API - 创建你的第一个组合式函数 - 探索 Vue 3 生态系统(Pinia、Vue Router 4)
Composition API 不仅仅是新语法 —— 它是一种思考 Vue 组件的新方式。拥抱它,你的代码将更清晰、更易维护、更具可扩展性。
关于作者: 本文使用 AI 辅助编写,采用 AI 优先开发工作流。所有代码示例都已使用 Vue 3.4+ 测试。