Vue 3 Composition API 深度解析
Composition API 让 Vue 应用更易于组织和重用逻辑。本文深入讲解这一核心特性。
核心概念
setup 函数
import { ref, computed, watch } from 'vue'
export default {
props: ['initialCount'],
emits: ['count-changed'],
setup(props, { emit, slots, expose }) {
// 创建响应式状态
const count = ref(props.initialCount)
const doubled = computed(() => count.value * 2)
// 监听状态变化
watch(count, (newVal, oldVal) => {
console.log(`Count changed from ${oldVal} to ${newVal}`)
emit('count-changed', newVal)
})
// 定义方法
const increment = () => count.value++
const decrement = () => count.value--
// 返回模板需要的内容
return {
count,
doubled,
increment,
decrement,
}
},
}
响应式基础
import { ref, reactive, readonly, isRef } from 'vue'
// ref - 用于基本类型
const count = ref(0)
console.log(count.value) // 0
count.value++
// reactive - 用于对象
const state = reactive({
name: 'John',
age: 30,
address: {
city: 'Beijing',
},
})
state.name = 'Jane' // 自动更新,无需 .value
// readonly - 创建只读副本
const original = reactive({ count: 0 })
const copy = readonly(original)
// copy.count++ // 错误:不能修改
// isRef 检查
console.log(isRef(count)) // true
console.log(isRef(state)) // false
组合式函数
创建可重用逻辑
// useCounter.js - 组合式函数
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const doubled = computed(() => count.value * 2)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
return {
count,
doubled,
increment,
decrement,
reset,
}
}
// useFetch.js - 数据获取组合式函数
import { ref, onMounted } from 'vue'
export function useFetch(url) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const fetch = async () => {
loading.value = true
error.value = null
try {
const response = await fetch(url)
data.value = await response.json()
} catch (e) {
error.value = e
} finally {
loading.value = false
}
}
onMounted(fetch)
return {
data,
loading,
error,
refetch: fetch,
}
}
// 使用
export default {
setup() {
const { count, doubled, increment } = useCounter(10)
const { data, loading, refetch } = useFetch('/api/data')
return {
count,
doubled,
increment,
data,
loading,
refetch,
}
},
}
生命周期钩子
Composition API 中的生命周期
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
} from 'vue'
export default {
setup() {
onBeforeMount(() => {
console.log('组件挂载前')
})
onMounted(() => {
console.log('组件已挂载')
// 初始化事件监听器、定时器等
})
onBeforeUpdate(() => {
console.log('组件更新前')
})
onUpdated(() => {
console.log('组件已更新')
})
onBeforeUnmount(() => {
console.log('组件卸载前')
})
onUnmounted(() => {
console.log('组件已卸载')
// 清理事件监听器、定时器等
})
onErrorCaptured((err, instance, info) => {
console.log('捕获错误:', err)
return false // 返回 false 阻止错误传播
})
return {}
},
}
模板引用
访问 DOM 元素
import { ref, onMounted } from 'vue'
export default {
setup() {
const inputRef = ref(null)
const listRef = ref(null)
const dynamicRef = ref(null)
onMounted(() => {
// 访问 DOM 元素
inputRef.value?.focus()
console.log(listRef.value?.offsetHeight)
})
// 函数式引用
const assignRef = el => {
if (el) {
console.log('元素已赋值', el)
} else {
console.log('元素已移除')
}
}
return {
inputRef,
listRef,
dynamicRef,
assignRef,
}
},
template: \`
<div>
<input ref="inputRef" />
<ul ref="listRef">
<li v-for="item in items" :key="item">{{ item }}</li>
</ul>
<div :ref="assignRef"></div>
</div>
\`,
}
依赖注入
跨组件共享数据
import { provide, inject, ref, readonly } from 'vue'
// 父组件
export default {
setup() {
const theme = ref('light')
const user = ref({ name: 'John', role: 'admin' })
// 提供数据给子组件
provide('theme', readonly(theme))
provide('updateTheme', (newTheme) => {
theme.value = newTheme
})
// 使用 Symbol 作为 key 避免命名冲突
const userKey = Symbol()
provide(userKey, readonly(user))
return {
theme,
updateTheme: (newTheme) => {
theme.value = newTheme
},
}
},
}
// 子组件
export default {
setup() {
// 注入数据
const theme = inject('theme')
const updateTheme = inject('updateTheme')
const user = inject(Symbol.for('user'))
// 带默认值的注入
const config = inject('config', {
apiUrl: 'http://localhost:3000',
})
return {
theme,
updateTheme,
user,
config,
}
},
}
高级状态管理
创建小型 store
import { reactive, readonly, computed } from 'vue'
// store.js - 不依赖 Pinia 的简单 store
export function createStore() {
const state = reactive({
items: [],
filter: 'all',
sortBy: 'date',
})
const filteredItems = computed(() => {
let result = state.items
if (state.filter !== 'all') {
result = result.filter(item => item.status === state.filter)
}
if (state.sortBy === 'date') {
result.sort((a, b) => new Date(b.date) - new Date(a.date))
} else if (state.sortBy === 'name') {
result.sort((a, b) => a.name.localeCompare(b.name))
}
return result
})
const actions = {
addItem(item) {
state.items.push({ ...item, id: Date.now() })
},
removeItem(id) {
state.items = state.items.filter(item => item.id !== id)
},
updateItem(id, updates) {
const item = state.items.find(item => item.id === id)
if (item) {
Object.assign(item, updates)
}
},
setFilter(filter) {
state.filter = filter
},
setSortBy(sortBy) {
state.sortBy = sortBy
},
}
return {
state: readonly(state),
filteredItems,
...actions,
}
}
// 使用
export default {
setup() {
const store = createStore()
const handleAdd = (item) => {
store.addItem(item)
}
return {
items: store.filteredItems,
addItem: handleAdd,
setFilter: store.setFilter,
}
},
}
最佳实践
✅ 应该做的事:
- 将相关逻辑组织在一起
- 创建可重用的组合式函数
- 使用 TypeScript 获得更好的类型检查
- 合理使用计算属性和监听器
- 及时清理事件监听器和定时器
❌ 不应该做的事:
- 在 setup 中执行副作用操作(除了生命周期钩子)
- 过度使用计算属性
- 忘记清理 watch 监听器
- 在 reactive 对象中存储引用类型时不谨慎
- 过度复杂化组合式函数
检查清单
- 正确使用 ref 和 reactive
- 生命周期钩子正确
- 模板引用工作正常
- 组合式函数可重用
- 性能优化得当


