Vue3 组合式 API 完全指南

Vue3 的组合式 API 为我们提供了一种更灵活、更强大的组件逻辑组织方式。本文将全面介绍组合式 API 的使用方法、最佳实践以及与选项式 API 的对比。
一、为什么需要组合式 API?
1.1 选项式 API 的局限性
在 Vue2 中,我们使用选项式 API 来组织组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <script> export default { data() { return { count: 0, user: null } }, computed: { doubleCount() { return this.count * 2 } }, methods: { increment() { this.count++ }, async fetchUser() { this.user = await fetchUser() } }, mounted() { this.fetchUser() } } </script>
|
存在的问题:
- 逻辑分散:相关逻辑被分散在 data、methods、computed 等选项中
- 代码复用困难:需要通过 Mixins 或高阶组件来实现
- TypeScript 支持有限:类型推断不够精确
- this 指向问题:箭头函数无法使用 this
1.2 组合式 API 的优势
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script setup> import { ref, computed, onMounted } from 'vue'
const count = ref(0) const user = ref(null)
const doubleCount = computed(() => count.value * 2)
function increment() { count.value++ }
async function fetchUser() { user.value = await fetchUser() }
onMounted(fetchUser) </script>
|
优势:
- 逻辑集中:相关代码可以组织在一起
- 更好的复用:通过组合函数实现逻辑复用
- 完美的 TypeScript 支持
- 没有 this 指向问题
二、核心 API 详解
2.1 ref 和 reactive
ref
ref 用于创建响应式的原始值或对象:
1 2 3 4 5 6 7 8 9
| import { ref } from 'vue'
const count = ref(0) const message = ref('Hello') const user = ref({ name: 'Alice', age: 25 })
console.log(count.value) count.value = 1
|
ref 的使用场景:
- 原始值(string, number, boolean)
- 需要整体替换的对象
- 在模板中直接使用(会自动解包)
reactive
reactive 用于创建响应式的对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { reactive } from 'vue'
const state = reactive({ count: 0, user: { name: 'Alice', age: 25 } })
console.log(state.count) state.count = 1 state.user.name = 'Bob'
|
reactive 的使用场景:
2.2 computed
computed 用于创建计算属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { ref, computed } from 'vue'
const count = ref(0) const doubleCount = computed(() => count.value * 2)
const count = ref(0) const doubleCount = computed({ get: () => count.value * 2, set: (val) => { count.value = val / 2 } })
doubleCount.value = 10 console.log(count.value)
|
computed 的特点:
- 基于响应式依赖缓存
- 只有依赖改变时才会重新计算
- 可以设置 getter 和 setter
2.3 watch 和 watchEffect
watch
watch 用于观察特定的数据源:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newValue, oldValue) => { console.log(`count changed from ${oldValue} to ${newValue}`) })
watch([count, message], ([newCount, newMessage]) => { console.log('Both changed') })
watch( () => state.user, (newUser) => { console.log('User changed', newUser) }, { deep: true, immediate: true, flush: 'post' } )
|
watchEffect
watchEffect 会自动追踪依赖:
1 2 3 4 5 6 7 8 9 10
| import { ref, watchEffect } from 'vue'
const count = ref(0)
watchEffect(() => { console.log(`Count is: ${count.value}`) })
count.value = 1
|
两者的区别:
- watch:明确指定要观察的数据源
- watchEffect:自动追踪函数内的响应式依赖
2.4 生命周期钩子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUpdate, onBeforeUnmount } from 'vue'
onMounted(() => { console.log('Component mounted') })
onUpdated(() => { console.log('Component updated') })
onUnmounted(() => { console.log('Component unmounted') })
|
三、组合函数(Composables)
3.1 什么是组合函数?
组合函数是利用 Vue 组合式 API 来封装和复用有状态逻辑的函数。
3.2 基础示例:鼠标位置追踪
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() { const x = ref(0) const y = ref(0)
function update(event) { x.value = event.pageX y.value = event.pageY }
onMounted(() => { window.addEventListener('mousemove', update) })
onUnmounted(() => { window.removeEventListener('mousemove', update) })
return { x, y } }
|
使用组合函数:
1 2 3 4 5 6 7 8 9 10 11
| <script setup> import { useMouse } from './useMouse'
const { x, y } = useMouse() </script>
<template> <div> Mouse position: {{ x }}, {{ y }} </div> </template>
|
3.3 进阶示例:数据请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { ref, watchEffect } from 'vue'
export function useFetch(url) { const data = ref(null) const error = ref(null) const loading = ref(false)
async function doFetch() { data.value = null error.value = null loading.value = true
try { const response = await fetch(url.value) data.value = await response.json() } catch (err) { error.value = err } finally { loading.value = false } }
watchEffect(doFetch)
return { data, error, loading } }
|
3.4 接受参数的组合函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import { ref, computed } from 'vue'
export function useCounter(initialValue = 0, step = 1) { const count = ref(initialValue)
const increment = () => { count.value += step }
const decrement = () => { count.value -= step }
const reset = () => { count.value = initialValue }
const double = computed(() => count.value * 2)
return { count, increment, decrement, reset, double } }
|
四、最佳实践
4.1 逻辑提取
将可复用的逻辑提取为独立的组合函数:
1 2 3 4 5 6 7 8 9 10 11 12
| import { useMouse } from './composables/useMouse' import { useScroll } from './composables/useScroll'
const { x, y } = useMouse() const { scrollTop } = useScroll()
const mousePos = { x: 0, y: 0 } const scrollPos = 0
|
4.2 命名规范
组合函数通常以 “use” 开头:
1 2 3 4 5 6 7 8 9 10
| useMouse() useFetch() useCounter() useLocalStorage()
mouse() fetchData() counter()
|
4.3 TypeScript 支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { ref, Ref } from 'vue'
interface MousePosition { x: Ref<number> y: Ref<number> }
export function useMouse(): MousePosition { const x = ref(0) const y = ref(0)
return { x, y } }
const { x, y } = useMouse()
|
五、与选项式 API 的混合使用
5.1 在选项式 API 中使用组合式 API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <script> import { ref } from 'vue'
export default { data() { return { optionData: '来自选项式 API' } }, setup() { const compositionData = ref('来自组合式 API') return { compositionData } }, methods: { handleClick() { console.log(this.optionData) console.log(this.compositionData) } } } </script>
|
5.2 迁移策略
- 渐进式迁移:新组件使用组合式 API,旧组件保持不变
- 提取逻辑:将可复用逻辑提取为组合函数
- 逐步重构:一次迁移一个组件
六、常见陷阱与解决方案
6.1 ref 解包问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const count = ref(0) const state = reactive({ count }) console.log(state.count)
import { toRefs } from 'vue' const state = reactive({ count: ref(0), message: ref('Hello') }) const { count, message } = toRefs(state)
|
6.2 响应式丢失
1 2 3 4 5 6 7 8 9
| const state = reactive({ count: 0, message: 'Hello' }) const { count, message } = state count++
import { toRefs } from 'vue' const { count, message } = toRefs(state) count.value++
|
6.3 深层响应式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const state = reactive({ user: { profile: { name: 'Alice' } } })
state.user.profile.name = 'Bob'
const state = ref({ user: { profile: { name: 'Alice' } } })
state.value.user.profile.name = 'Bob'
import { shallowRef, triggerRef } from 'vue' const state = shallowRef({ count: 0 }) state.value = { count: 1 } state.value.count = 2
|
七、性能优化
7.1 使用 shallowRef 和 shallowReactive
1 2 3 4 5 6 7
| import { shallowRef, shallowReactive } from 'vue'
const largeObject = shallowRef({ })
largeObject.value = { ...largeObject.value, updated: true }
|
7.2 合理使用 computed
1 2 3 4 5 6 7
| const expensiveValue = computed(() => { return heavyComputation(state.value) })
<div>{{ heavyComputation(state) }}</div>
|
八、总结
Vue3 的组合式 API 为我们提供了更强大、更灵活的代码组织方式。通过组合函数,我们可以更好地复用逻辑、维护代码。
关键要点:
- 使用 ref 处理原始值,reactive 处理对象
- 通过组合函数实现逻辑复用
- 使用 toRefs 避免响应式丢失
- 合理使用 computed 和 watch
- 注意性能优化,避免不必要的响应式
掌握组合式 API 是成为 Vue3 高级开发者的必经之路。