Appearance
Vue 3 Composition API 最佳实践
Vue 3 的 Composition API 为我们提供了一种全新的组件逻辑组织方式。相比于 Options API,Composition API 在代码复用、类型推导和逻辑组织方面都有显著优势。本文将深入探讨 Composition API 的最佳实践。
为什么选择 Composition API?
1. 更好的逻辑复用
在 Options API 中,我们通常使用 mixins 来复用逻辑,但这会带来命名冲突和来源不明的问题。Composition API 通过组合函数(Composables)提供了更优雅的解决方案。
javascript
// 传统的 mixin 方式
const counterMixin = {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
// Composition API 方式
function useCounter() {
const count = ref(0)
const increment = () => count.value++
return {
count,
increment
}
}
2. 更好的类型推导
TypeScript 用户会发现 Composition API 提供了更好的类型推导支持:
typescript
import { ref, computed } from 'vue'
function useUser() {
const user = ref<User | null>(null)
const isLoggedIn = computed(() => !!user.value)
const login = async (credentials: LoginCredentials) => {
// TypeScript 能够正确推导类型
user.value = await authService.login(credentials)
}
return {
user,
isLoggedIn,
login
}
}
核心概念详解
响应式基础
ref vs reactive
javascript
import { ref, reactive } from 'vue'
// ref: 用于基本类型和单个值
const count = ref(0)
const message = ref('Hello')
// reactive: 用于对象和数组
const state = reactive({
user: null,
loading: false,
error: null
})
// 访问值的区别
console.log(count.value) // 需要 .value
console.log(state.user) // 直接访问
toRefs 的妙用
当需要解构 reactive 对象时,使用 toRefs 保持响应性:
javascript
import { reactive, toRefs } from 'vue'
function useUserState() {
const state = reactive({
user: null,
loading: false,
error: null
})
// 保持响应性的解构
return {
...toRefs(state),
// 其他方法...
}
}
计算属性和侦听器
computed 的高级用法
javascript
import { ref, computed } 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
javascript
import { ref, watch, watchEffect } from 'vue'
const count = ref(0)
const doubled = ref(0)
// watch: 明确指定依赖
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
// watchEffect: 自动收集依赖
watchEffect(() => {
doubled.value = count.value * 2
})
// 侦听多个源
watch([count, doubled], ([newCount, newDoubled]) => {
console.log(`count: ${newCount}, doubled: ${newDoubled}`)
})
最佳实践
1. 组合函数的设计原则
单一职责
每个组合函数应该只负责一个特定的功能:
javascript
// ✅ 好的做法:单一职责
function useCounter() {
const count = ref(0)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = 0
return { count, increment, decrement, reset }
}
function useLocalStorage(key, defaultValue) {
const storedValue = localStorage.getItem(key)
const value = ref(storedValue ? JSON.parse(storedValue) : defaultValue)
watch(value, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
return value
}
// ❌ 不好的做法:职责混乱
function useCounterWithStorage() {
// 混合了计数器和存储逻辑
}
返回值的一致性
javascript
// ✅ 推荐:返回对象,便于解构和重命名
function useApi(url) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const fetch = async () => {
loading.value = true
try {
const response = await api.get(url)
data.value = response.data
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
return {
data,
loading,
error,
fetch
}
}
// 使用时可以重命名
const { data: userData, loading: userLoading } = useApi('/users')
2. 生命周期钩子的使用
javascript
import { onMounted, onUnmounted, ref } from 'vue'
function useEventListener(target, event, handler) {
onMounted(() => {
target.addEventListener(event, handler)
})
onUnmounted(() => {
target.removeEventListener(event, handler)
})
}
function useWindowSize() {
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. 错误处理
javascript
function useAsyncOperation() {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const execute = async (operation) => {
loading.value = true
error.value = null
try {
data.value = await operation()
} catch (err) {
error.value = err
console.error('Operation failed:', err)
} finally {
loading.value = false
}
}
return {
data: readonly(data),
loading: readonly(loading),
error: readonly(error),
execute
}
}
4. 性能优化
使用 shallowRef 和 shallowReactive
javascript
import { shallowRef, shallowReactive } from 'vue'
// 对于大型对象,如果只需要替换而不需要深度响应
const largeData = shallowRef({})
// 对于只需要第一层响应的对象
const config = shallowReactive({
theme: 'dark',
language: 'zh-CN',
features: {
// 这一层不会是响应式的
enableNotifications: true
}
})
合理使用 markRaw
javascript
import { markRaw, reactive } from 'vue'
const state = reactive({
user: null,
// 第三方库实例不需要响应式
chartInstance: markRaw(new Chart())
})
实际应用示例
表单处理
javascript
import { ref, reactive, computed } from 'vue'
function useForm(initialValues, validationRules) {
const values = reactive({ ...initialValues })
const errors = reactive({})
const touched = reactive({})
const isValid = computed(() => {
return Object.keys(errors).length === 0
})
const validate = (field) => {
const rule = validationRules[field]
if (rule) {
const error = rule(values[field])
if (error) {
errors[field] = error
} else {
delete errors[field]
}
}
}
const handleChange = (field, value) => {
values[field] = value
touched[field] = true
validate(field)
}
const handleSubmit = (onSubmit) => {
// 验证所有字段
Object.keys(validationRules).forEach(validate)
if (isValid.value) {
onSubmit(values)
}
}
return {
values,
errors,
touched,
isValid,
handleChange,
handleSubmit
}
}
数据获取
javascript
function useQuery(queryFn, options = {}) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const { immediate = true, refetchOnWindowFocus = false } = options
const execute = async () => {
loading.value = true
error.value = null
try {
data.value = await queryFn()
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
if (immediate) {
execute()
}
if (refetchOnWindowFocus) {
useEventListener(window, 'focus', execute)
}
return {
data: readonly(data),
loading: readonly(loading),
error: readonly(error),
refetch: execute
}
}
总结
Composition API 为 Vue 3 带来了强大的逻辑组织能力。通过遵循以下原则,我们可以写出更加清晰、可维护的代码:
- 单一职责:每个组合函数专注于一个功能
- 一致的返回值:使用对象返回,便于解构和重命名
- 合理的抽象:在复用性和简单性之间找到平衡
- 性能考虑:合理使用 shallow 系列 API
- 错误处理:提供完善的错误处理机制
Composition API 不是银弹,但它确实为我们提供了更多的可能性。在实际项目中,我们可以根据具体需求选择最合适的方案。
相关文章推荐: