引言
Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。这一新特性不仅解决了 Vue 2 中选项式 API 的诸多限制,还为开发者提供了更灵活、更强大的组件开发方式。本文将深入探讨 Composition API 的核心特性,演示如何构建可复用的组合式函数、实现复杂的状态管理,并通过实际案例展示如何优化组件结构,提升开发效率和代码质量。
Composition API 核心概念
什么是 Composition API
Composition API 是 Vue 3 中引入的一种新的组件开发方式,它允许我们以函数的形式组织和复用组件逻辑。与 Vue 2 中的选项式 API(Options API)不同,Composition API 将组件的逻辑按功能进行组织,而不是按选项类型分组。
核心函数介绍
Composition API 提供了一系列核心函数,这些函数是构建复杂应用的基础:
import {
ref,
reactive,
computed,
watch,
watchEffect,
onMounted,
onUpdated,
onUnmounted,
provide,
inject
} from 'vue'
这些函数提供了从响应式数据到生命周期钩子的完整功能支持。
响应式数据管理
Ref 与 Reactive 的区别
在 Composition API 中,ref 和 reactive 是两种主要的响应式数据创建方式:
import { ref, reactive } from 'vue'
// 使用 ref 创建响应式数据
const count = ref(0)
const name = ref('Vue')
// 使用 reactive 创建响应式对象
const state = reactive({
count: 0,
name: 'Vue',
user: {
age: 20,
email: 'vue@example.com'
}
})
// 访问值时的区别
console.log(count.value) // 0
console.log(state.count) // 0
深层响应式与浅层响应式
import { shallowRef, triggerRef } from 'vue'
// 浅层响应式
const shallow = shallowRef({
nested: { count: 0 }
})
// 修改嵌套对象不会触发更新
shallow.value.nested.count = 1 // 不会触发更新
// 手动触发更新
triggerRef(shallow)
组合式函数(Composable Functions)
构建可复用的逻辑
组合式函数是 Composition API 的核心概念之一,它允许我们将可复用的逻辑封装成函数,供多个组件使用:
// composables/useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
const reset = () => {
count.value = initialValue
}
const doubleCount = computed(() => count.value * 2)
return {
count,
increment,
decrement,
reset,
doubleCount
}
}
// 在组件中使用
import { useCounter } from '@/composables/useCounter'
export default {
setup() {
const { count, increment, decrement, doubleCount } = useCounter(10)
return {
count,
increment,
decrement,
doubleCount
}
}
}
复杂状态管理示例
// composables/useApi.js
import { ref, computed } from 'vue'
export function useApi(url) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchData = 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
}
}
const hasData = computed(() => !!data.value)
const hasError = computed(() => !!error.value)
return {
data,
loading,
error,
fetchData,
hasData,
hasError
}
}
// 在组件中使用
import { useApi } from '@/composables/useApi'
export default {
setup() {
const { data, loading, error, fetchData, hasData } = useApi('/api/users')
fetchData()
return {
data,
loading,
error,
hasData
}
}
}
生命周期钩子
组件生命周期管理
Composition API 提供了与 Vue 2 相同的生命周期钩子,但以函数形式提供:
import {
onMounted,
onUpdated,
onUnmounted,
onBeforeMount,
onBeforeUpdate,
onBeforeUnmount
} from 'vue'
export default {
setup() {
// 组件挂载时执行
onMounted(() => {
console.log('组件已挂载')
// 初始化定时器等操作
})
// 组件更新时执行
onUpdated(() => {
console.log('组件已更新')
})
// 组件卸载前执行
onBeforeUnmount(() => {
console.log('组件即将卸载')
// 清理定时器等操作
})
// 组件卸载时执行
onUnmounted(() => {
console.log('组件已卸载')
})
return {}
}
}
异步数据获取最佳实践
// composables/useAsyncData.js
import { ref, watch } from 'vue'
export function useAsyncData(fetcher, options = {}) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const refresh = ref(0)
const fetchData = async (params = {}) => {
loading.value = true
error.value = null
try {
const result = await fetcher(params)
data.value = result
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
// 监听刷新计数器
watch(refresh, () => {
fetchData()
})
// 自动获取数据
fetchData()
return {
data,
loading,
error,
refresh: () => refresh.value++,
fetchData
}
}
状态管理与依赖注入
Provide/Inject 机制
Provide/Inject 是 Vue 中实现跨层级组件通信的重要机制:
// 父组件
import { provide, reactive } from 'vue'
export default {
setup() {
const globalState = reactive({
user: null,
theme: 'light',
language: 'zh-CN'
})
provide('globalState', globalState)
provide('updateTheme', (theme) => {
globalState.theme = theme
})
return {
globalState
}
}
}
// 子组件
import { inject } from 'vue'
export default {
setup() {
const globalState = inject('globalState')
const updateTheme = inject('updateTheme')
const switchTheme = () => {
const newTheme = globalState.theme === 'light' ? 'dark' : 'light'
updateTheme(newTheme)
}
return {
globalState,
switchTheme
}
}
}
复杂状态管理示例
// composables/useGlobalStore.js
import { reactive, readonly } from 'vue'
// 创建全局状态
const state = reactive({
user: null,
theme: 'light',
language: 'zh-CN',
notifications: []
})
// 提供只读状态访问
const readonlyState = readonly(state)
// 提供状态更新方法
const mutations = {
setUser(user) {
state.user = user
},
setTheme(theme) {
state.theme = theme
},
addNotification(notification) {
state.notifications.push(notification)
},
removeNotification(id) {
const index = state.notifications.findIndex(n => n.id === id)
if (index > -1) {
state.notifications.splice(index, 1)
}
}
}
// 提供全局状态访问
export function useGlobalStore() {
return {
state: readonlyState,
...mutations
}
}
组件复用策略
逻辑复用的最佳实践
// composables/useForm.js
import { ref, reactive } from 'vue'
export function useForm(initialData = {}) {
const formData = reactive({ ...initialData })
const errors = ref({})
const isSubmitting = ref(false)
const validate = (rules = {}) => {
const newErrors = {}
Object.keys(rules).forEach(field => {
const rule = rules[field]
const value = formData[field]
if (rule.required && !value) {
newErrors[field] = `${field} 是必填项`
}
if (rule.minLength && value && value.length < rule.minLength) {
newErrors[field] = `${field} 长度不能少于 ${rule.minLength} 位`
}
})
errors.value = newErrors
return Object.keys(newErrors).length === 0
}
const submit = async (submitHandler) => {
if (!validate()) return false
isSubmitting.value = true
try {
const result = await submitHandler(formData)
return result
} catch (error) {
console.error('提交失败:', error)
return false
} finally {
isSubmitting.value = false
}
}
const reset = () => {
Object.keys(formData).forEach(key => {
formData[key] = initialData[key] || ''
})
errors.value = {}
}
return {
formData,
errors,
isSubmitting,
validate,
submit,
reset
}
}
// 在组件中使用
import { useForm } from '@/composables/useForm'
export default {
setup() {
const {
formData,
errors,
isSubmitting,
validate,
submit,
reset
} = useForm({
username: '',
email: '',
password: ''
})
const rules = {
username: { required: true, minLength: 3 },
email: { required: true, minLength: 5 },
password: { required: true, minLength: 6 }
}
const handleSubmit = async (data) => {
// 实际的提交逻辑
console.log('提交数据:', data)
return { success: true }
}
const handleSave = async () => {
const isValid = validate(rules)
if (isValid) {
const result = await submit(handleSubmit)
if (result.success) {
// 处理成功逻辑
console.log('保存成功')
}
}
}
return {
formData,
errors,
isSubmitting,
handleSave,
reset
}
}
}
组件组合模式
// composables/usePagination.js
import { ref, computed } from 'vue'
export function usePagination(total = 0, pageSize = 10) {
const currentPage = ref(1)
const pageSizeRef = ref(pageSize)
const totalPage = computed(() => {
return Math.ceil(total / pageSizeRef.value)
})
const hasNext = computed(() => {
return currentPage.value < totalPage.value
})
const hasPrev = computed(() => {
return currentPage.value > 1
})
const goToPage = (page) => {
if (page >= 1 && page <= totalPage.value) {
currentPage.value = page
}
}
const next = () => {
if (hasNext.value) {
currentPage.value++
}
}
const prev = () => {
if (hasPrev.value) {
currentPage.value--
}
}
const setPageSize = (size) => {
pageSizeRef.value = size
currentPage.value = 1
}
return {
currentPage,
pageSize: pageSizeRef,
totalPage,
hasNext,
hasPrev,
goToPage,
next,
prev,
setPageSize
}
}
// 在组件中使用
import { usePagination } from '@/composables/usePagination'
export default {
setup() {
const {
currentPage,
totalPage,
hasNext,
hasPrev,
next,
prev,
goToPage
} = usePagination(100, 10)
return {
currentPage,
totalPage,
hasNext,
hasPrev,
next,
prev,
goToPage
}
}
}
性能优化与最佳实践
计算属性与监听器优化
import {
computed,
watch,
watchEffect,
shallowRef,
markRaw
} from 'vue'
// 使用 watchEffect 优化监听
export default {
setup() {
const count = ref(0)
const name = ref('Vue')
// watchEffect 会自动追踪依赖
watchEffect(() => {
console.log(`count: ${count.value}, name: ${name.value}`)
})
// 使用 shallowRef 优化复杂对象
const complexData = shallowRef({
items: [],
metadata: {}
})
// 只监听特定属性
watch(() => count.value, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`)
})
return {
count,
name
}
}
}
避免重复计算
// composables/useMemo.js
import { computed, ref } from 'vue'
export function useMemo(computation, dependencies) {
const cache = ref(null)
const hasCache = ref(false)
const result = computed(() => {
if (!hasCache.value) {
cache.value = computation()
hasCache.value = true
}
return cache.value
})
// 依赖变化时清除缓存
watch(dependencies, () => {
hasCache.value = false
})
return result
}
// 使用示例
export default {
setup() {
const data = ref([])
const filter = ref('')
const filteredData = useMemo(() => {
return data.value.filter(item =>
item.name.toLowerCase().includes(filter.value.toLowerCase())
)
}, [data, filter])
return {
filteredData
}
}
}
实际应用案例
电商购物车实现
// composables/useShoppingCart.js
import { ref, computed, watch } from 'vue'
export function useShoppingCart() {
const items = ref([])
// 添加商品到购物车
const addToCart = (product, quantity = 1) => {
const existingItem = items.value.find(item => item.id === product.id)
if (existingItem) {
existingItem.quantity += quantity
} else {
items.value.push({
...product,
quantity
})
}
}
// 移除商品
const removeFromCart = (productId) => {
items.value = items.value.filter(item => item.id !== productId)
}
// 更新商品数量
const updateQuantity = (productId, quantity) => {
const item = items.value.find(item => item.id === productId)
if (item) {
item.quantity = Math.max(0, quantity)
if (item.quantity === 0) {
removeFromCart(productId)
}
}
}
// 计算总价
const totalPrice = computed(() => {
return items.value.reduce((total, item) => {
return total + (item.price * item.quantity)
}, 0)
})
// 计算商品总数
const totalItems = computed(() => {
return items.value.reduce((total, item) => total + item.quantity, 0)
})
// 清空购物车
const clearCart = () => {
items.value = []
}
// 保存到本地存储
watch(items, (newItems) => {
localStorage.setItem('shoppingCart', JSON.stringify(newItems))
}, { deep: true })
// 从本地存储恢复
const restoreFromStorage = () => {
const saved = localStorage.getItem('shoppingCart')
if (saved) {
items.value = JSON.parse(saved)
}
}
return {
items,
addToCart,
removeFromCart,
updateQuantity,
totalPrice,
totalItems,
clearCart,
restoreFromStorage
}
}
// 在组件中使用
import { useShoppingCart } from '@/composables/useShoppingCart'
export default {
setup() {
const {
items,
addToCart,
removeFromCart,
updateQuantity,
totalPrice,
totalItems,
clearCart,
restoreFromStorage
} = useShoppingCart()
// 恢复购物车状态
restoreFromStorage()
return {
items,
addToCart,
removeFromCart,
updateQuantity,
totalPrice,
totalItems,
clearCart
}
}
}
用户认证系统
// composables/useAuth.js
import { ref, computed } from 'vue'
export function useAuth() {
const user = ref(null)
const token = ref(null)
const loading = ref(false)
const error = ref(null)
// 登录
const login = async (credentials) => {
loading.value = true
error.value = null
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(credentials)
})
const data = await response.json()
if (response.ok) {
user.value = data.user
token.value = data.token
localStorage.setItem('authToken', data.token)
return { success: true }
} else {
throw new Error(data.message || '登录失败')
}
} catch (err) {
error.value = err.message
return { success: false, error: err.message }
} finally {
loading.value = false
}
}
// 注销
const logout = () => {
user.value = null
token.value = null
localStorage.removeItem('authToken')
}
// 检查认证状态
const checkAuth = () => {
const savedToken = localStorage.getItem('authToken')
if (savedToken) {
token.value = savedToken
// 这里可以添加验证 token 的逻辑
user.value = { name: 'User' } // 模拟用户数据
}
}
// 检查用户是否已登录
const isAuthenticated = computed(() => !!user.value)
// 获取用户信息
const userInfo = computed(() => user.value)
// 获取访问令牌
const accessToken = computed(() => token.value)
// 初始化认证状态
checkAuth()
return {
user: userInfo,
token: accessToken,
loading,
error,
login,
logout,
isAuthenticated,
checkAuth
}
}
总结
Vue 3 的 Composition API 为前端开发带来了革命性的变化,它不仅解决了 Vue 2 中选项式 API 的诸多限制,还提供了更灵活、更强大的组件开发方式。通过合理使用组合式函数、响应式数据管理、生命周期钩子和状态管理机制,我们可以构建出更加模块化、可复用和易于维护的组件。
在实际开发中,建议遵循以下最佳实践:
- 合理组织逻辑:将相关的功能逻辑封装成组合式函数,提高代码复用性
- 优化响应式数据:根据数据特点选择合适的响应式创建方式
- 合理使用生命周期:在适当的时机执行初始化和清理操作
- 性能优化:合理使用计算属性、监听器和缓存机制
- 状态管理:对于复杂应用,可以结合 provide/inject 或状态管理库实现全局状态管理
通过深入理解和掌握 Composition API 的核心特性,开发者可以构建出更加高效、可维护的 Vue 应用程序,提升开发效率和代码质量。

评论 (0)