Appearance
Vue 3 Composition API 最佳实践
Vue 3 的 Composition API 为我们提供了更灵活、更强大的组件逻辑组织方式。本文将深入探讨其核心概念和最佳实践。
为什么选择 Composition API
相比于 Options API,Composition API 具有以下优势:
- 更好的逻辑复用:通过组合函数实现逻辑复用
- 更好的类型推导:TypeScript 支持更加完善
- 更灵活的代码组织:相关逻辑可以组织在一起
- 更小的打包体积:支持 tree-shaking
核心概念
ref vs reactive
typescript
import { ref, reactive } from 'vue'
// ref:用于基本类型
const count = ref(0)
const message = ref('Hello')
// reactive:用于对象类型
const state = reactive({
user: {
name: 'John',
age: 30
},
posts: []
})
computed 计算属性
typescript
import { computed, ref } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// 只读计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 可写计算属性
const fullNameWritable = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(value) {
[firstName.value, lastName.value] = value.split(' ')
}
})
watch 和 watchEffect
typescript
import { watch, watchEffect, ref } from 'vue'
const count = ref(0)
const message = ref('hello')
// watch:明确指定依赖
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
// 监听多个源
watch([count, message], ([newCount, newMessage], [oldCount, oldMessage]) => {
console.log('Multiple values changed')
})
// watchEffect:自动收集依赖
watchEffect(() => {
console.log(`Count is ${count.value}, message is ${message.value}`)
})
最佳实践
1. 组合函数设计原则
单一职责原则
typescript
// ✅ 好的做法:单一职责
function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
return {
count: readonly(count),
increment,
decrement,
reset
}
}
// ❌ 避免:职责混乱
function useCounterAndUser() {
// 计数器逻辑
const count = ref(0)
// 用户逻辑
const user = ref(null)
// ...
}
一致的返回值
typescript
// ✅ 好的做法:一致的返回模式
function useApi<T>(url: string) {
const data = ref<T | null>(null)
const loading = ref(false)
const error = ref<string | null>(null)
const execute = async () => {
loading.value = true
error.value = null
try {
const response = await fetch(url)
data.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
return {
data: readonly(data),
loading: readonly(loading),
error: readonly(error),
execute
}
}
2. 生命周期钩子
typescript
import { onMounted, onUnmounted, onUpdated } from 'vue'
function useEventListener(target: EventTarget, event: string, handler: Function) {
onMounted(() => {
target.addEventListener(event, handler)
})
onUnmounted(() => {
target.removeEventListener(event, handler)
})
}
// 使用
function useWindowResize() {
const width = ref(window.innerWidth)
const height = ref(window.innerHeight)
const updateSize = () => {
width.value = window.innerWidth
height.value = window.innerHeight
}
useEventListener(window, 'resize', updateSize)
return { width, height }
}
3. 错误处理
typescript
function useAsyncOperation<T>() {
const data = ref<T | null>(null)
const loading = ref(false)
const error = ref<Error | null>(null)
const execute = async (operation: () => Promise<T>) => {
try {
loading.value = true
error.value = null
data.value = await operation()
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err))
console.error('Async operation failed:', err)
} finally {
loading.value = false
}
}
const reset = () => {
data.value = null
error.value = null
loading.value = false
}
return {
data: readonly(data),
loading: readonly(loading),
error: readonly(error),
execute,
reset
}
}
性能优化
1. 使用 shallowRef 和 shallowReactive
typescript
import { shallowRef, shallowReactive } from 'vue'
// 对于大型对象,使用 shallow 版本
const largeList = shallowRef([])
const config = shallowReactive({
theme: 'dark',
language: 'zh-CN'
})
2. 使用 markRaw
typescript
import { markRaw, reactive } from 'vue'
const state = reactive({
// 第三方库实例不需要响应式
chart: markRaw(new Chart()),
data: []
})
3. 合理使用 computed
typescript
// ✅ 好的做法:缓存复杂计算
const expensiveValue = computed(() => {
return heavyCalculation(props.data)
})
// ❌ 避免:在模板中直接调用函数
// <template>
// <div>{{ heavyCalculation(data) }}</div>
// </template>
实际应用示例
表单处理
typescript
function useForm<T extends Record<string, any>>(initialValues: T) {
const values = reactive({ ...initialValues })
const errors = reactive<Partial<Record<keyof T, string>>>({})
const touched = reactive<Partial<Record<keyof T, boolean>>>({})
const setFieldValue = (field: keyof T, value: any) => {
values[field] = value
touched[field] = true
// 清除错误
if (errors[field]) {
delete errors[field]
}
}
const setFieldError = (field: keyof T, error: string) => {
errors[field] = error
}
const validate = (rules: Partial<Record<keyof T, (value: any) => string | undefined>>) => {
let isValid = true
Object.keys(rules).forEach(field => {
const rule = rules[field as keyof T]
if (rule) {
const error = rule(values[field as keyof T])
if (error) {
setFieldError(field as keyof T, error)
isValid = false
}
}
})
return isValid
}
const reset = () => {
Object.assign(values, initialValues)
Object.keys(errors).forEach(key => delete errors[key])
Object.keys(touched).forEach(key => delete touched[key])
}
return {
values,
errors: readonly(errors),
touched: readonly(touched),
setFieldValue,
setFieldError,
validate,
reset
}
}
数据获取
typescript
function useFetch<T>(url: string, options?: RequestInit) {
const data = ref<T | null>(null)
const loading = ref(false)
const error = ref<string | null>(null)
const execute = async () => {
loading.value = true
error.value = null
try {
const response = await fetch(url, options)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
data.value = await response.json()
} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error'
} finally {
loading.value = false
}
}
// 自动执行
onMounted(execute)
return {
data: readonly(data),
loading: readonly(loading),
error: readonly(error),
refetch: execute
}
}
总结
Composition API 为 Vue 3 带来了更强大的逻辑组织能力。通过遵循最佳实践,我们可以:
- 编写更可维护的代码
- 实现更好的逻辑复用
- 获得更好的 TypeScript 支持
- 优化应用性能
记住,好的代码不仅要功能正确,还要易于理解和维护。Composition API 为我们提供了实现这一目标的工具,但如何使用这些工具仍然需要我们的智慧和经验。