引言
Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。相比于传统的 Options API,Composition API 提供了更加灵活和强大的组件开发方式,特别是在处理复杂逻辑、响应式数据管理和组件通信方面表现出了显著优势。
在现代前端开发中,如何高效地管理响应式数据、实现组件间的通信,以及构建可维护的代码结构,已经成为开发者面临的重要挑战。Composition API 的出现为这些问题提供了优雅的解决方案。本文将深入探讨 Vue 3 Composition API 的核心概念和使用技巧,并通过实际项目案例展示现代化 Vue 开发的最佳实践。
Vue 3 Composition API 核心概念
什么是 Composition API
Composition API 是 Vue 3 中引入的一种新的组件开发方式,它允许开发者以函数的形式组织和复用逻辑代码。与传统的 Options API(基于选项的对象配置)不同,Composition API 更加灵活,能够更好地处理复杂的组件逻辑。
Composition API 的核心思想是将组件的逻辑按照功能进行分组,而不是按照 Vue 选项进行分组。这种设计使得代码更加模块化、可重用,并且更容易维护和测试。
响应式系统基础
在深入 Composition API 之前,我们需要理解 Vue 3 的响应式系统。Vue 3 使用了基于 ES6 Proxy 的响应式系统,这比 Vue 2 中的 Object.defineProperty 更加强大和灵活。
import { reactive, ref, computed } from 'vue'
// ref 用于基本数据类型
const count = ref(0)
console.log(count.value) // 0
// reactive 用于对象和数组
const state = reactive({
name: 'Vue',
version: 3
})
console.log(state.name) // Vue
响应式数据管理实战
Ref 与 Reactive 的使用场景
在 Composition API 中,ref 和 reactive 是两个核心的响应式函数,它们各自有不同的使用场景。
import { ref, reactive, computed } from 'vue'
export default {
setup() {
// 使用 ref 管理基本数据类型
const count = ref(0)
const message = ref('Hello Vue 3')
// 使用 reactive 管理复杂对象
const user = reactive({
name: 'John',
age: 25,
address: {
city: 'Beijing',
country: 'China'
}
})
// 响应式计算属性
const doubledCount = computed(() => count.value * 2)
// 更新数据的方法
const increment = () => {
count.value++
}
return {
count,
message,
user,
doubledCount,
increment
}
}
}
复杂状态管理
对于复杂的状态管理,我们可以创建专门的组合函数来封装逻辑:
// 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 doubled = computed(() => count.value * 2)
return {
count,
increment,
decrement,
reset,
doubled
}
}
// composables/useUser.js
import { ref, reactive } from 'vue'
export function useUser() {
const user = reactive({
profile: {
name: '',
email: '',
avatar: ''
},
permissions: [],
loading: false
})
const fetchUser = async (userId) => {
user.loading = true
try {
// 模拟 API 调用
const response = await fetch(`/api/users/${userId}`)
const data = await response.json()
Object.assign(user.profile, data)
} catch (error) {
console.error('Failed to fetch user:', error)
} finally {
user.loading = false
}
}
const updateUser = async (userData) => {
try {
const response = await fetch('/api/users', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
})
const updatedUser = await response.json()
Object.assign(user.profile, updatedUser)
} catch (error) {
console.error('Failed to update user:', error)
}
}
return {
user,
fetchUser,
updateUser
}
}
状态共享与数据流
在大型应用中,合理的状态管理至关重要。我们可以结合 Composition API 和全局状态管理模式来实现:
// stores/globalStore.js
import { reactive } from 'vue'
export const globalStore = reactive({
theme: 'light',
language: 'zh-CN',
notifications: [],
setTheme(theme) {
this.theme = theme
},
addNotification(notification) {
this.notifications.push({
id: Date.now(),
...notification,
timestamp: new Date()
})
},
removeNotification(id) {
const index = this.notifications.findIndex(n => n.id === id)
if (index > -1) {
this.notifications.splice(index, 1)
}
}
})
// composables/useGlobalStore.js
import { globalStore } from '@/stores/globalStore'
export function useGlobalStore() {
return {
theme: globalStore.theme,
language: globalStore.language,
notifications: globalStore.notifications,
setTheme: globalStore.setTheme.bind(globalStore),
addNotification: globalStore.addNotification.bind(globalStore),
removeNotification: globalStore.removeNotification.bind(globalStore)
}
}
组件间通信最佳实践
Props 与 Emit 的高级用法
在 Composition API 中,props 和 emit 的使用方式与 Options API 相似,但更加灵活:
// ChildComponent.vue
import { computed, watch } from 'vue'
export default {
props: {
title: {
type: String,
required: true
},
data: {
type: Array,
default: () => []
},
isActive: {
type: Boolean,
default: false
}
},
emits: ['update:data', 'item-selected', 'close'],
setup(props, { emit }) {
// 使用 computed 处理 props
const formattedTitle = computed(() => {
return props.title.toUpperCase()
})
// 监听 props 变化
watch(() => props.data, (newData, oldData) => {
console.log('Data changed:', newData)
})
// 发送事件
const handleItemSelect = (item) => {
emit('item-selected', item)
}
const handleClose = () => {
emit('close')
}
return {
formattedTitle,
handleItemSelect,
handleClose
}
}
}
Provide / Inject 的使用
Provide/Inject 是 Vue 中实现跨层级组件通信的重要机制,在 Composition API 中的使用更加简洁:
// ParentComponent.vue
import { provide, reactive } from 'vue'
export default {
setup() {
const appState = reactive({
user: null,
theme: 'light',
locale: 'zh-CN'
})
// 提供数据给子组件
provide('appState', appState)
provide('updateTheme', (theme) => {
appState.theme = theme
})
return {
appState
}
}
}
// ChildComponent.vue
import { inject } from 'vue'
export default {
setup() {
// 注入数据
const appState = inject('appState')
const updateTheme = inject('updateTheme')
const switchTheme = () => {
const newTheme = appState.theme === 'light' ? 'dark' : 'light'
updateTheme(newTheme)
}
return {
appState,
switchTheme
}
}
}
全局事件总线模式
对于需要跨组件通信的场景,我们可以创建一个全局事件系统:
// utils/eventBus.js
import { reactive } from 'vue'
export const eventBus = reactive({
events: {},
on(event, callback) {
if (!this.events[event]) {
this.events[event] = []
}
this.events[event].push(callback)
},
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback)
}
},
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data))
}
}
})
// composables/useEventBus.js
import { eventBus } from '@/utils/eventBus'
export function useEventBus() {
return {
on: eventBus.on.bind(eventBus),
off: eventBus.off.bind(eventBus),
emit: eventBus.emit.bind(eventBus)
}
}
// 在组件中使用
// MyComponent.vue
import { useEventBus } from '@/composables/useEventBus'
import { watch } from 'vue'
export default {
setup() {
const { on, off, emit } = useEventBus()
// 监听全局事件
const handleGlobalEvent = (data) => {
console.log('Received global event:', data)
}
on('custom-event', handleGlobalEvent)
// 组件销毁时清理监听器
watch(() => {
off('custom-event', handleGlobalEvent)
})
return {
emitCustomEvent: (data) => emit('custom-event', data)
}
}
}
生命周期钩子与副作用管理
生命周期钩子的使用
Composition API 提供了与 Vue 2 相同的生命周期钩子,但以函数的形式调用:
import {
onMounted,
onUpdated,
onUnmounted,
onActivated,
onDeactivated,
watch,
watchEffect
} from 'vue'
export default {
setup() {
// 组件挂载时
onMounted(() => {
console.log('Component mounted')
// 初始化第三方库
initializeChart()
})
// 组件更新时
onUpdated(() => {
console.log('Component updated')
// 更新 DOM 或重新计算
updateLayout()
})
// 组件卸载时
onUnmounted(() => {
console.log('Component unmounted')
// 清理定时器、事件监听等
cleanup()
})
// 组件激活时(keep-alive)
onActivated(() => {
console.log('Component activated')
// 恢复状态
resumeState()
})
// 组件失活时(keep-alive)
onDeactivated(() => {
console.log('Component deactivated')
// 保存状态
saveState()
})
return {}
}
}
副作用管理
副作用管理是 Composition API 的重要特性,通过 watch 和 watchEffect 来处理响应式数据的变化:
import { ref, watch, watchEffect } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('Vue')
const user = ref(null)
// 基础 watch - 只监听特定的响应式数据
watch(count, (newVal, oldVal) => {
console.log(`Count changed from ${oldVal} to ${newVal}`)
})
// 监听多个数据源
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log(`Count: ${oldCount} -> ${newCount}, Name: ${oldName} -> ${newName}`)
})
// 深度监听对象
watch(user, (newUser) => {
console.log('User updated:', newUser)
}, { deep: true })
// 立即执行的 watch
watch(count, (newVal) => {
console.log('Immediate watch:', newVal)
}, { immediate: true })
// watchEffect - 自动追踪依赖
watchEffect(() => {
// 自动追踪所有响应式数据的使用
console.log(`Name: ${name.value}, Count: ${count.value}`)
})
// 停止监听器
const stopWatch = watch(count, (newVal) => {
console.log('Watching count:', newVal)
})
// 在适当的时候停止监听
// stopWatch()
return {
count,
name,
user
}
}
}
实际项目案例分析
复杂表单管理场景
让我们通过一个实际的用户管理系统来展示 Composition API 的强大功能:
// composables/useUserForm.js
import { ref, reactive, computed, watch } from 'vue'
export function useUserForm(initialData = {}) {
// 表单数据
const formData = reactive({
name: initialData.name || '',
email: initialData.email || '',
age: initialData.age || null,
phone: initialData.phone || '',
address: {
street: initialData.address?.street || '',
city: initialData.address?.city || '',
country: initialData.address?.country || ''
},
isActive: initialData.isActive || false
})
// 表单验证规则
const validationRules = {
name: (value) => value.length >= 2,
email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
age: (value) => value >= 0 && value <= 150,
phone: (value) => /^[\d\s\-\(\)]+$/.test(value)
}
// 验证状态
const validationErrors = reactive({})
// 表单是否有效
const isValid = computed(() => {
return Object.values(validationErrors).every(error => !error)
})
// 表单是否已修改
const isDirty = computed(() => {
return JSON.stringify(formData) !== JSON.stringify(initialData)
})
// 验证单个字段
const validateField = (field, value) => {
if (validationRules[field]) {
const isValid = validationRules[field](value)
validationErrors[field] = !isValid ? `${field} is invalid` : ''
return isValid
}
return true
}
// 验证所有字段
const validateAll = () => {
Object.keys(formData).forEach(key => {
if (key === 'address') {
Object.keys(formData.address).forEach(addrKey => {
validateField(`${key}.${addrKey}`, formData[key][addrKey])
})
} else {
validateField(key, formData[key])
}
})
return isValid.value
}
// 监听表单变化并实时验证
watch(formData, () => {
validateAll()
}, { deep: true })
// 重置表单
const reset = () => {
Object.assign(formData, initialData)
Object.keys(validationErrors).forEach(key => {
delete validationErrors[key]
})
}
// 获取表单数据
const getFormData = () => {
return { ...formData }
}
// 设置表单数据
const setFormData = (data) => {
Object.assign(formData, data)
}
return {
formData,
validationErrors,
isValid,
isDirty,
validateField,
validateAll,
reset,
getFormData,
setFormData
}
}
// UserForm.vue
<template>
<div class="user-form">
<form @submit.prevent="handleSubmit">
<div class="form-group">
<label>姓名:</label>
<input
v-model="formData.name"
type="text"
:class="{ error: validationErrors.name }"
/>
<span v-if="validationErrors.name" class="error-message">{{ validationErrors.name }}</span>
</div>
<div class="form-group">
<label>邮箱:</label>
<input
v-model="formData.email"
type="email"
:class="{ error: validationErrors.email }"
/>
<span v-if="validationErrors.email" class="error-message">{{ validationErrors.email }}</span>
</div>
<div class="form-group">
<label>年龄:</label>
<input
v-model.number="formData.age"
type="number"
:class="{ error: validationErrors.age }"
/>
<span v-if="validationErrors.age" class="error-message">{{ validationErrors.age }}</span>
</div>
<div class="form-group">
<label>电话:</label>
<input
v-model="formData.phone"
type="tel"
:class="{ error: validationErrors.phone }"
/>
<span v-if="validationErrors.phone" class="error-message">{{ validationErrors.phone }}</span>
</div>
<div class="form-group">
<label>地址:</label>
<input
v-model="formData.address.street"
placeholder="街道"
/>
<input
v-model="formData.address.city"
placeholder="城市"
/>
<input
v-model="formData.address.country"
placeholder="国家"
/>
</div>
<div class="form-group">
<label>
<input
v-model="formData.isActive"
type="checkbox"
/>
激活状态
</label>
</div>
<div class="form-actions">
<button type="submit" :disabled="!isValid || isSubmitting">保存</button>
<button type="button" @click="reset">重置</button>
</div>
</form>
</div>
</template>
<script>
import { useUserForm } from '@/composables/useUserForm'
export default {
props: {
initialData: {
type: Object,
default: () => ({})
}
},
setup(props) {
const {
formData,
validationErrors,
isValid,
isDirty,
reset,
validateAll
} = useUserForm(props.initialData)
const isSubmitting = ref(false)
const handleSubmit = async () => {
if (!validateAll()) {
return
}
isSubmitting.value = true
try {
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 1000))
console.log('Form submitted:', formData)
// 这里可以调用实际的 API
} catch (error) {
console.error('Submit failed:', error)
} finally {
isSubmitting.value = false
}
}
return {
formData,
validationErrors,
isValid,
isDirty,
reset,
handleSubmit,
isSubmitting
}
}
}
</script>
<style scoped>
.user-form {
max-width: 500px;
margin: 0 auto;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
.form-group input {
width: 100%;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
}
.form-group input.error {
border-color: #ff4757;
}
.error-message {
color: #ff4757;
font-size: 0.875rem;
}
.form-actions {
margin-top: 2rem;
text-align: center;
}
.form-actions button {
margin: 0 0.5rem;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.form-actions button[type="submit"] {
background-color: #3742fa;
color: white;
}
.form-actions button[type="button"] {
background-color: #6c757d;
color: white;
}
.form-actions button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
数据获取与缓存管理
在实际应用中,数据获取和缓存管理是非常重要的功能:
// composables/useApi.js
import { ref, reactive } from 'vue'
export function useApi() {
const loading = ref(false)
const error = ref(null)
// 缓存机制
const cache = reactive(new Map())
const cacheTimeout = 5 * 60 * 1000 // 5分钟缓存
const fetchWithCache = async (url, options = {}) => {
const cacheKey = `${url}_${JSON.stringify(options)}`
// 检查缓存
if (cache.has(cacheKey)) {
const cached = cache.get(cacheKey)
if (Date.now() - cached.timestamp < cacheTimeout) {
return cached.data
} else {
cache.delete(cacheKey)
}
}
try {
loading.value = true
error.value = null
const response = await fetch(url, options)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
// 缓存数据
cache.set(cacheKey, {
data,
timestamp: Date.now()
})
return data
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
const clearCache = () => {
cache.clear()
}
const invalidateCache = (url) => {
for (const [key, value] of cache.entries()) {
if (key.startsWith(url)) {
cache.delete(key)
}
}
}
return {
loading,
error,
fetchWithCache,
clearCache,
invalidateCache
}
}
// composables/useUserList.js
import { ref, computed } from 'vue'
import { useApi } from '@/composables/useApi'
export function useUserList() {
const { loading, error, fetchWithCache } = useApi()
const users = ref([])
const page = ref(1)
const pageSize = ref(10)
const total = ref(0)
const searchQuery = ref('')
const paginatedUsers = computed(() => {
return users.value.slice(
(page.value - 1) * pageSize.value,
page.value * pageSize.value
)
})
const totalPages = computed(() => {
return Math.ceil(total.value / pageSize.value)
})
const fetchUsers = async (options = {}) => {
try {
const {
page: pageNum = 1,
search = '',
limit = pageSize.value
} = options
page.value = pageNum
searchQuery.value = search
const params = new URLSearchParams({
page: pageNum,
limit,
search
})
const data = await fetchWithCache(`/api/users?${params}`)
users.value = data.users || data.data || []
total.value = data.total || data.count || 0
return data
} catch (err) {
console.error('Failed to fetch users:', err)
throw err
}
}
const refreshUsers = () => {
return fetchUsers({ page: 1 })
}
const nextPage = () => {
if (page.value < totalPages.value) {
return fetchUsers({ page: page.value + 1 })
}
}
const prevPage = () => {
if (page.value > 1) {
return fetchUsers({ page: page.value - 1 })
}
}
return {
users,
paginatedUsers,
loading,
error,
page,
pageSize,
total,
totalPages,
searchQuery,
fetchUsers,
refreshUsers,
nextPage,
prevPage
}
}
性能优化与最佳实践
组合函数的复用性设计
好的组合函数应该具备良好的复用性和可配置性:
// composables/useDataLoader.js
import { ref, reactive, watch } from 'vue'
export function useDataLoader(initialConfig = {}) {
const config = reactive({
url: '',
method: 'GET',
headers: {},
params: {},
autoLoad: true,
...initialConfig
})
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const timestamp = ref(null)
// 加载数据的方法
const load = async (customConfig = {}) => {
try {
const finalConfig = { ...config, ...customConfig }
loading.value = true
error.value = null
const url = new URL(finalConfig.url)
// 添加查询参数
Object.entries(finalConfig.params).forEach(([key, value]) => {
url.searchParams.append(key, value)
})
const response = await fetch(url.toString(), {
method: finalConfig.method,
headers: finalConfig.headers,
...finalConfig.options
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const result = await response.json()
data.value = result
timestamp.value = Date.now()
return result
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
// 刷新数据
const refresh = () => {
return load()
}
// 配置更新
const updateConfig = (newConfig) => {
Object.assign(config, newConfig)
}
// 监听配置变化并自动加载
if (config.autoLoad) {
watch(() => config.url, () => {
if (config.url) {
load()
}
})
}
return {
data,
loading,
error,
timestamp,
load,
refresh,
updateConfig
}
}
响应式数据的性能监控
在大型应用中,对响应式数据的变化进行监控可以帮助我们发现潜在的性能问题:
// utils/performanceMonitor.js
import { watch } from 'vue'
export class PerformanceMonitor {
constructor() {
this.watchers = []
this.metrics = {
watchCount: 0,
averageWatchTime: 0,
maxWatchTime: 0
}
}
addWatcher(target, callback, options = {}) {
const startTime = performance.now()
const watcher = watch(target, (newVal, oldVal) => {
const endTime = performance.now()
const duration = endTime - startTime
this.metrics.watchCount++
this.metrics.averageWatchTime =
(this.metrics.averageWatchTime * (this.metrics.watchCount - 1) + duration) /
this.metrics.watchCount
this.metrics.maxWatchTime = Math.max(this.metrics.maxWatchTime, duration
评论 (0)