Vue 3 Composition API 实战指南
Vue 3 的 Composition API 彻底改变了我们组织 Vue 组件代码的方式。相比 Options API,它提供了更灵活的逻辑复用和更好的 TypeScript 支持。本文将通过实际案例,带你深入理解 Composition API 的核心概念与最佳实践。
一、为什么需要 Composition API?
Options API 在处理复杂组件时存在明显痛点:
- 逻辑分散:同一功能的代码被拆分到 data、methods、computed、watch 等选项中;
- 复用困难:Mixins 存在命名冲突、来源不清晰等问题;
- TypeScript 支持差:this 上下文推断复杂。
Composition API 允许我们按功能组织代码,并通过组合函数(Composables)实现逻辑复用。
二、核心 API 详解
1. ref 与 reactive
这是响应式数据的两种创建方式:
import { ref, reactive } from 'vue'
// ref:适用于基本类型,访问需要 .value
const count = ref(0)
console.log(count.value) // 0
count.value++
// reactive:适用于对象,直接访问属性
const state = reactive({
name: '水码',
age: 25
})
console.log(state.name) // '水码'
state.age = 26
💡 建议:优先使用 ref,因为它在解构、传递时不会丢失响应性。
2. computed 计算属性
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
// 只读计算属性
const fullName = computed(() => firstName.value + lastName.value)
// 可写计算属性
const fullNameWritable = computed({
get: () => firstName.value + lastName.value,
set: (val) => {
firstName.value = val[0]
lastName.value = val.slice(1)
}
})
3. watch 与 watchEffect
import { ref, watch, watchEffect } from 'vue'
const keyword = ref('')
// watch:显式指定监听源
watch(keyword, (newVal, oldVal) => {
console.log(`搜索词从 ${oldVal} 变为 ${newVal}`)
}, { immediate: true })
// watchEffect:自动收集依赖
watchEffect(() => {
console.log(`当前搜索词:${keyword.value}`)
// 自动追踪 keyword.value
})
4. 生命周期钩子
import { onMounted, onUnmounted, onUpdated } from 'vue'
onMounted(() => {
console.log('组件已挂载')
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
console.log('组件将卸载')
window.removeEventListener('resize', handleResize)
})
onUpdated(() => {
console.log('组件已更新')
})
三、组合函数(Composables)实战
这是 Composition API 最强大的特性——将逻辑封装为可复用的函数:
示例:useMouse 鼠标位置追踪
// composables/useMouse.ts
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(e: MouseEvent) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
// 在组件中使用
<script setup>
import { useMouse } from '@/composables/useMouse'
const { x, y } = useMouse()
</script>
<template>
<p>鼠标位置:{{ x }}, {{ y }}</p>
</template>
示例:useFetch 数据请求
// composables/useFetch.ts
import { ref, watchEffect, toValue } from 'vue'
export function useFetch<T>(url: string | Ref<string>) {
const data = ref<T | null>(null)
const error = ref<Error | null>(null)
const loading = ref(false)
async function fetchData() {
loading.value = true
error.value = null
try {
const res = await fetch(toValue(url))
data.value = await res.json()
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
}
watchEffect(() => {
fetchData()
})
return { data, error, loading, refetch: fetchData }
}
四、<script setup> 语法糖
Vue 3.2 引入的 <script setup> 是 Composition API 的编译时语法糖,大幅简化代码:
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useMouse } from '@/composables/useMouse'
// 直接声明响应式变量
const count = ref(0)
const double = computed(() => count.value * 2)
// 组合函数
const { x, y } = useMouse()
// 定义 props 和 emits
const props = defineProps<{
title: string
count?: number
}>()
const emit = defineEmits<{
(e: 'update', value: number): void
}>()
// 函数直接可用于模板
function increment() {
count.value++
emit('update', count.value)
}
</script>
<template>
<h1>{{ title }}</h1>
<button @click="increment">{{ count }} × 2 = {{ double }}</button>
</template>
五、最佳实践
- 按功能组织代码:相关逻辑放在一起,而非按选项类型分散;
- 命名规范:组合函数以
use开头,如useAuth、useCart; - 保持纯粹:组合函数应专注单一职责,避免副作用耦合;
- 善用 TypeScript:充分利用类型推断和泛型;
- 避免过度抽象:不要为复用而复用,简单逻辑留在组件内即可。
六、学习资源
- 官方文档:Composition API FAQ
- VueUse:vueuse.org(200+ 组合函数集合)
- Vue 3 迁移指南:v3-migration.vuejs.org
Composition API 不仅是语法的升级,更是思维方式的转变。掌握它,你将写出更清晰、更可维护、更易测试的 Vue 代码。现在就开始重构你的组件吧!