引言
Vue 3 的发布带来了全新的 Composition API,这一创新性的 API 设计理念彻底改变了我们编写 Vue 组件的方式。相比于传统的 Options API,Composition API 提供了更加灵活、可复用的代码组织方式,特别是在处理复杂业务逻辑时展现出显著优势。
本文将深入探讨 Vue 3 Composition API 的核心概念,并通过实际项目案例演示如何构建响应式数据流、实现组件间通信以及进行逻辑复用。我们将从基础概念入手,逐步深入到高级特性,帮助开发者掌握这一现代前端开发工具的核心技能。
Vue 3 Composition API 核心概念
什么是 Composition API
Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式。它允许我们通过组合函数来组织和复用组件逻辑,而不是按照传统的选项(options)方式将代码分散在 data、methods、computed 等不同选项中。
核心响应式函数
Composition API 的核心是几个基础的响应式函数:
ref():创建响应式的数据引用reactive():创建响应式的对象computed():创建计算属性watch():监听数据变化watchEffect():自动追踪依赖的变化
import { ref, reactive, computed, watch } from 'vue'
// 创建响应式数据
const count = ref(0)
const person = reactive({
name: 'John',
age: 30
})
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 监听器
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`)
})
响应式数据管理实战
基础响应式数据操作
在实际开发中,我们需要处理各种复杂的响应式数据场景。让我们通过一个用户管理系统来演示基础操作:
// userStore.js
import { ref, reactive, computed } from 'vue'
export function useUserStore() {
// 用户列表
const users = ref([])
// 当前选中的用户
const selectedUser = ref(null)
// 搜索关键词
const searchKeyword = ref('')
// 添加用户
const addUser = (user) => {
users.value.push({ ...user, id: Date.now() })
}
// 删除用户
const deleteUser = (id) => {
users.value = users.value.filter(user => user.id !== id)
}
// 更新用户
const updateUser = (id, updates) => {
const index = users.value.findIndex(user => user.id === id)
if (index !== -1) {
users.value[index] = { ...users.value[index], ...updates }
}
}
// 搜索用户
const filteredUsers = computed(() => {
if (!searchKeyword.value) return users.value
return users.value.filter(user =>
user.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
user.email.toLowerCase().includes(searchKeyword.value.toLowerCase())
)
})
return {
users,
selectedUser,
searchKeyword,
addUser,
deleteUser,
updateUser,
filteredUsers
}
}
复杂数据结构管理
在实际项目中,我们经常需要处理嵌套的复杂数据结构。以下是一个购物车系统的实现:
// cartStore.js
import { ref, computed } from 'vue'
export function useCartStore() {
// 购物车商品列表
const items = ref([])
// 添加商品到购物车
const addToCart = (product) => {
const existingItem = items.value.find(item => item.id === product.id)
if (existingItem) {
existingItem.quantity += 1
} else {
items.value.push({
...product,
quantity: 1
})
}
}
// 移除商品
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 = []
}
return {
items,
addToCart,
removeFromCart,
updateQuantity,
totalPrice,
totalItems,
clearCart
}
}
组件间通信模式
父子组件通信
在 Vue 3 中,父子组件通信可以通过 props 和 emit 实现,结合 Composition API 可以更好地组织代码:
<!-- Parent.vue -->
<template>
<div class="parent">
<h2>父组件</h2>
<Child
:user-data="userData"
@update-user="handleUpdateUser"
@delete-user="handleDeleteUser"
/>
<button @click="addNewUser">添加用户</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const userData = ref({
name: '张三',
age: 25,
email: 'zhangsan@example.com'
})
const handleUpdateUser = (updatedData) => {
userData.value = { ...userData.value, ...updatedData }
}
const handleDeleteUser = () => {
userData.value = null
}
const addNewUser = () => {
userData.value = {
name: '新用户',
age: 30,
email: 'newuser@example.com'
}
}
</script>
<!-- Child.vue -->
<template>
<div class="child">
<h3>子组件</h3>
<div v-if="userData">
<p>姓名: {{ userData.name }}</p>
<p>年龄: {{ userData.age }}</p>
<p>邮箱: {{ userData.email }}</p>
<button @click="updateUser">更新用户</button>
<button @click="deleteUser">删除用户</button>
</div>
<div v-else>
<p>暂无用户数据</p>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
userData: {
type: Object,
default: null
}
})
const emit = defineEmits(['updateUser', 'deleteUser'])
const updateUser = () => {
emit('updateUser', {
name: props.userData.name + ' (更新)',
age: props.userData.age + 1
})
}
const deleteUser = () => {
emit('deleteUser')
}
</script>
兄弟组件通信
在兄弟组件间通信时,可以使用事件总线或状态管理方案。以下是一个基于 provide/inject 的实现:
<!-- App.vue -->
<template>
<div class="app">
<ComponentA />
<ComponentB />
</div>
</template>
<script setup>
import { provide, ref } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'
// 创建共享状态
const sharedMessage = ref('Hello from App')
const sharedData = ref({ count: 0 })
// 提供给子组件
provide('sharedMessage', sharedMessage)
provide('sharedData', sharedData)
// 更新共享数据的方法
const updateSharedData = (newData) => {
sharedData.value = { ...sharedData.value, ...newData }
}
provide('updateSharedData', updateSharedData)
</script>
<!-- ComponentA.vue -->
<template>
<div class="component-a">
<h3>组件 A</h3>
<p>共享消息: {{ sharedMessage }}</p>
<p>共享数据: {{ sharedData.count }}</p>
<button @click="incrementCount">增加计数</button>
</div>
</template>
<script setup>
import { inject } from 'vue'
const sharedMessage = inject('sharedMessage')
const sharedData = inject('sharedData')
const updateSharedData = inject('updateSharedData')
const incrementCount = () => {
updateSharedData({ count: sharedData.value.count + 1 })
}
</script>
<!-- ComponentB.vue -->
<template>
<div class="component-b">
<h3>组件 B</h3>
<p>共享消息: {{ sharedMessage }}</p>
<p>共享数据: {{ sharedData.count }}</p>
<button @click="decrementCount">减少计数</button>
</div>
</template>
<script setup>
import { inject } from 'vue'
const sharedMessage = inject('sharedMessage')
const sharedData = inject('sharedData')
const updateSharedData = inject('updateSharedData')
const decrementCount = () => {
updateSharedData({ count: sharedData.value.count - 1 })
}
</script>
逻辑复用与自定义组合式函数
创建可复用的组合式函数
Vue 3 的 Composition API 最大的优势之一就是能够轻松地创建可复用的逻辑。让我们创建一个数据获取和加载状态管理的组合式函数:
// useApi.js
import { ref, computed } from 'vue'
export function useApi(apiFunction, initialData = null) {
const data = ref(initialData)
const loading = ref(false)
const error = ref(null)
const fetchData = async (...args) => {
try {
loading.value = true
error.value = null
const result = await apiFunction(...args)
data.value = result
} catch (err) {
error.value = err.message || '请求失败'
console.error('API Error:', err)
} finally {
loading.value = false
}
}
const refresh = () => {
if (data.value !== null) {
fetchData()
}
}
const clear = () => {
data.value = initialData
error.value = null
}
// 计算属性:检查是否已加载数据
const hasData = computed(() => data.value !== null)
// 计算属性:检查是否有错误
const hasError = computed(() => error.value !== null)
return {
data,
loading,
error,
fetchData,
refresh,
clear,
hasData,
hasError
}
}
实际应用示例
让我们使用这个组合式函数来创建一个用户列表组件:
<!-- UserList.vue -->
<template>
<div class="user-list">
<div class="controls">
<input
v-model="searchQuery"
placeholder="搜索用户..."
@keyup.enter="searchUsers"
/>
<button @click="searchUsers">搜索</button>
<button @click="refreshData" :disabled="loading">刷新</button>
</div>
<div v-if="loading" class="loading">
加载中...
</div>
<div v-else-if="hasError" class="error">
错误: {{ error }}
</div>
<div v-else-if="!hasData" class="empty">
暂无用户数据
</div>
<ul v-else class="users">
<li v-for="user in data" :key="user.id" class="user-item">
<h4>{{ user.name }}</h4>
<p>{{ user.email }}</p>
<p>年龄: {{ user.age }}</p>
</li>
</ul>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useApi } from './composables/useApi'
// 模拟 API 调用
const fetchUsers = async (query = '') => {
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 1000))
// 模拟返回数据
const mockUsers = [
{ id: 1, name: '张三', email: 'zhangsan@example.com', age: 25 },
{ id: 2, name: '李四', email: 'lisi@example.com', age: 30 },
{ id: 3, name: '王五', email: 'wangwu@example.com', age: 35 }
]
if (query) {
return mockUsers.filter(user =>
user.name.toLowerCase().includes(query.toLowerCase()) ||
user.email.toLowerCase().includes(query.toLowerCase())
)
}
return mockUsers
}
const { data, loading, error, fetchData, refresh, hasData, hasError } = useApi(fetchUsers)
const searchQuery = ref('')
// 当搜索查询变化时自动搜索
watch(searchQuery, (newQuery) => {
if (newQuery.trim() !== '') {
searchUsers()
}
})
const searchUsers = () => {
fetchData(searchQuery.value)
}
const refreshData = () => {
refresh()
}
// 组件挂载时获取数据
fetchData()
</script>
<style scoped>
.user-list {
padding: 20px;
}
.controls {
margin-bottom: 20px;
display: flex;
gap: 10px;
align-items: center;
}
.loading, .error, .empty {
text-align: center;
padding: 20px;
}
.error {
color: #ff4757;
}
.users {
list-style: none;
padding: 0;
}
.user-item {
border: 1px solid #e0e0e0;
margin-bottom: 10px;
padding: 15px;
border-radius: 4px;
}
</style>
高级组合式函数模式
我们还可以创建更复杂的组合式函数来处理特定的业务场景。以下是一个处理表单验证的组合式函数:
// useFormValidation.js
import { ref, computed } from 'vue'
export function useFormValidation(initialForm = {}) {
const form = ref({ ...initialForm })
const errors = ref({})
const isSubmitting = ref(false)
// 验证规则
const rules = ref({})
// 设置验证规则
const setRules = (newRules) => {
rules.value = newRules
}
// 验证单个字段
const validateField = (fieldName) => {
const value = form.value[fieldName]
const fieldRules = rules.value[fieldName] || []
for (const rule of fieldRules) {
if (!rule.validator(value)) {
errors.value[fieldName] = rule.message
return false
}
}
delete errors.value[fieldName]
return true
}
// 验证整个表单
const validateForm = () => {
let isValid = true
errors.value = {}
Object.keys(rules.value).forEach(fieldName => {
if (!validateField(fieldName)) {
isValid = false
}
})
return isValid
}
// 设置表单值
const setFieldValue = (fieldName, value) => {
form.value[fieldName] = value
// 实时验证
validateField(fieldName)
}
// 重置表单
const resetForm = () => {
form.value = { ...initialForm }
errors.value = {}
}
// 提交表单
const submitForm = async (submitHandler) => {
if (!validateForm()) {
return false
}
isSubmitting.value = true
try {
await submitHandler(form.value)
return true
} catch (err) {
console.error('提交失败:', err)
return false
} finally {
isSubmitting.value = false
}
}
// 表单是否有效
const isValid = computed(() => {
return Object.keys(errors.value).length === 0 &&
Object.keys(form.value).every(key => form.value[key] !== '')
})
return {
form,
errors,
isSubmitting,
setRules,
validateField,
validateForm,
setFieldValue,
resetForm,
submitForm,
isValid
}
}
状态管理最佳实践
响应式数据的性能优化
在处理大量响应式数据时,我们需要考虑性能优化:
// useOptimizedState.js
import { ref, shallowRef, computed, watch } from 'vue'
export function useOptimizedState(initialValue) {
// 对于大型对象,使用 shallowRef 可以避免深度响应式追踪
const state = shallowRef(initialValue)
// 只在需要时才创建计算属性
const computedValue = computed(() => {
// 复杂的计算逻辑
return state.value.data?.map(item => ({
...item,
processed: true
}))
})
// 使用 watch 的 flush 选项优化性能
const watchEffectOptimized = (callback, options = {}) => {
return watch(state, callback, {
flush: options.flush || 'pre', // pre, post, sync
deep: options.deep || false,
immediate: options.immediate || false
})
}
const updateState = (newData) => {
state.value = newData
}
return {
state,
computedValue,
watchEffectOptimized,
updateState
}
}
数据持久化处理
在实际应用中,我们经常需要将响应式数据持久化到本地存储:
// usePersistence.js
import { ref, watch } from 'vue'
export function usePersistence(key, initialValue) {
const persisted = ref(initialValue)
// 从本地存储加载数据
const loadFromStorage = () => {
try {
const stored = localStorage.getItem(key)
if (stored) {
persisted.value = JSON.parse(stored)
}
} catch (error) {
console.error(`Failed to load ${key} from localStorage`, error)
}
}
// 保存到本地存储
const saveToStorage = () => {
try {
localStorage.setItem(key, JSON.stringify(persisted.value))
} catch (error) {
console.error(`Failed to save ${key} to localStorage`, error)
}
}
// 监听数据变化并保存
watch(persisted, saveToStorage, { deep: true })
// 初始化时加载数据
loadFromStorage()
return persisted
}
错误处理与调试
响应式数据的错误边界
在处理响应式数据时,我们需要考虑错误处理:
// useSafeRef.js
import { ref } from 'vue'
export function useSafeRef(initialValue) {
const value = ref(initialValue)
// 安全的值设置函数
const safeSet = (newValue) => {
try {
value.value = newValue
} catch (error) {
console.error('Failed to set value:', error)
// 可以选择恢复到之前的值或使用默认值
}
}
// 安全的值获取函数
const safeGet = () => {
try {
return value.value
} catch (error) {
console.error('Failed to get value:', error)
return null
}
}
return {
value,
safeSet,
safeGet
}
}
调试工具集成
Vue 3 提供了强大的调试工具支持:
// useDebug.js
import { watch, onMounted, onUnmounted } from 'vue'
export function useDebug(name, data) {
// 在组件挂载时输出调试信息
onMounted(() => {
console.log(`[DEBUG] ${name} mounted`, data)
})
// 监听数据变化
watch(data, (newVal, oldVal) => {
console.log(`[DEBUG] ${name} changed`, { old: oldVal, new: newVal })
}, { deep: true })
// 在组件卸载时输出调试信息
onUnmounted(() => {
console.log(`[DEBUG] ${name} unmounted`)
})
}
实际项目应用案例
完整的电商购物车系统
让我们通过一个完整的电商购物车系统来展示所有概念的实际应用:
<!-- ShoppingCart.vue -->
<template>
<div class="shopping-cart">
<h2>购物车</h2>
<div v-if="loading" class="loading">加载中...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<div v-else>
<div class="cart-summary">
<p>商品数量: {{ totalItems }}</p>
<p>总价: ¥{{ totalPrice.toFixed(2) }}</p>
<button @click="clearCart" :disabled="totalItems === 0">清空购物车</button>
</div>
<div class="cart-items">
<div
v-for="item in items"
:key="item.id"
class="cart-item"
>
<img :src="item.image" :alt="item.name" />
<div class="item-details">
<h4>{{ item.name }}</h4>
<p>¥{{ item.price.toFixed(2) }}</p>
<div class="quantity-controls">
<button @click="updateQuantity(item.id, item.quantity - 1)">-</button>
<span>{{ item.quantity }}</span>
<button @click="updateQuantity(item.id, item.quantity + 1)">+</button>
</div>
<button @click="removeFromCart(item.id)" class="remove-btn">删除</button>
</div>
</div>
</div>
<div v-if="items.length === 0" class="empty-cart">
购物车为空
</div>
</div>
</div>
</template>
<script setup>
import { computed, onMounted } from 'vue'
import { useCartStore } from './stores/cartStore'
const {
items,
totalPrice,
totalItems,
addToCart,
removeFromCart,
updateQuantity,
clearCart,
loading,
error
} = useCartStore()
// 模拟初始化数据
onMounted(() => {
// 这里可以加载持久化的购物车数据
})
</script>
<style scoped>
.shopping-cart {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.cart-summary {
background: #f5f5f5;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.loading, .error, .empty-cart {
text-align: center;
padding: 40px;
font-size: 18px;
}
.error {
color: #ff4757;
}
.cart-items {
display: flex;
flex-direction: column;
gap: 15px;
}
.cart-item {
display: flex;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 15px;
align-items: center;
gap: 15px;
}
.item-details {
flex: 1;
}
.quantity-controls {
display: flex;
align-items: center;
gap: 10px;
margin: 10px 0;
}
.quantity-controls button {
width: 30px;
height: 30px;
border: none;
background: #007bff;
color: white;
border-radius: 4px;
cursor: pointer;
}
.remove-btn {
background: #ff4757;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
}
</style>
总结
Vue 3 Composition API 为我们提供了一种更加灵活和强大的组件开发方式。通过本文的实践演示,我们可以看到:
-
响应式数据管理:使用
ref、reactive等函数可以轻松创建和管理复杂的响应式数据结构。 -
组件间通信:通过 props、emit、provide/inject 等机制,可以实现多种组件间通信模式。
-
逻辑复用:组合式函数让代码复用变得更加简单和直观,提高了开发效率。
-
状态管理最佳实践:合理的性能优化、错误处理和调试支持是构建稳定应用的关键。
-
实际项目应用:通过完整的购物车系统案例,展示了如何将所有概念整合到实际开发中。
掌握 Vue 3 Composition API 不仅能提高代码质量,还能让团队协作更加高效。建议在实际项目中积极采用这些最佳实践,逐步提升前端开发的技术水平和工程化能力。随着 Vue 生态的不断发展,Composition API 将继续发挥重要作用,为现代前端开发提供更强大的支持。

评论 (0)