引言
Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。相比于传统的 Options API,Composition API 提供了更加灵活和强大的组件状态管理能力,特别是在处理复杂业务逻辑时表现尤为突出。本文将深入探讨 Vue 3 Composition API 的高级用法,通过实际项目案例展示如何构建高效、可维护的 Vue 应用。
什么是 Composition API
Composition API 是 Vue 3 中引入的一种新的组件开发方式,它允许我们使用函数来组织和复用组件逻辑。与 Options API 相比,Composition API 更加灵活,能够更好地处理复杂的组件逻辑,特别是在需要跨组件共享逻辑的情况下。
Composition API 的核心概念
- 响应式数据管理:通过
ref和reactive管理响应式数据 - 生命周期钩子:使用
onMounted、onUpdated等函数处理生命周期 - 组合式函数:将可复用的逻辑封装成组合式函数
- 计算属性和监听器:使用
computed和watch进行数据计算和监听
响应式数据管理详解
ref 与 reactive 的区别与使用
在 Vue 3 中,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: {
firstName: 'John',
lastName: 'Doe'
}
})
// 在模板中使用
// <template>
// <p>{{ count }}</p>
// <p>{{ name }}</p>
// <p>{{ state.count }}</p>
// </template>
// 修改数据
count.value = 10
state.count = 20
复杂对象的响应式处理
对于嵌套的对象和数组,需要特别注意响应式的处理方式:
import { ref, reactive } from 'vue'
// 复杂对象的处理
const user = ref({
profile: {
name: 'John',
age: 30,
address: {
city: 'Beijing',
country: 'China'
}
},
preferences: ['reading', 'coding']
})
// 修改嵌套属性
user.value.profile.name = 'Jane'
user.value.profile.address.city = 'Shanghai'
// 数组操作
const items = ref([1, 2, 3])
items.value.push(4) // 正确的数组操作方式
// 对于深层嵌套的对象,可以使用 toRefs 进行解构
import { toRefs } from 'vue'
const userState = reactive({
profile: {
name: 'John',
age: 30
}
})
const { profile } = toRefs(userState)
// profile 现在是响应式的 ref 对象
组件状态管理最佳实践
全局状态管理的实现
在大型应用中,合理的全局状态管理至关重要。我们可以使用 Composition API 来构建自己的状态管理方案:
// stores/userStore.js
import { ref, computed } from 'vue'
export function useUserStore() {
const currentUser = ref(null)
const isLoggedIn = computed(() => !!currentUser.value)
const login = (userData) => {
currentUser.value = userData
}
const logout = () => {
currentUser.value = null
}
const updateProfile = (profileData) => {
if (currentUser.value) {
currentUser.value.profile = { ...currentUser.value.profile, ...profileData }
}
}
return {
currentUser,
isLoggedIn,
login,
logout,
updateProfile
}
}
// 在组件中使用
import { useUserStore } from '@/stores/userStore'
export default {
setup() {
const { currentUser, isLoggedIn, login, logout } = useUserStore()
const handleLogin = () => {
login({
id: 1,
name: 'John Doe',
email: 'john@example.com'
})
}
return {
currentUser,
isLoggedIn,
handleLogin,
logout
}
}
}
状态持久化方案
为了提升用户体验,我们需要将状态持久化到本地存储中:
// utils/statePersistence.js
import { ref, watch } from 'vue'
export function usePersistentState(key, defaultValue) {
const state = ref(defaultValue)
// 从 localStorage 恢复状态
const savedState = localStorage.getItem(key)
if (savedState) {
try {
state.value = JSON.parse(savedState)
} catch (error) {
console.error('Failed to parse saved state:', error)
}
}
// 监听状态变化并保存到 localStorage
watch(state, (newState) => {
localStorage.setItem(key, JSON.stringify(newState))
}, { deep: true })
return state
}
// 使用示例
export function useTodoStore() {
const todos = usePersistentState('todos', [])
const filter = usePersistentState('todoFilter', 'all')
const addTodo = (text) => {
todos.value.push({
id: Date.now(),
text,
completed: false
})
}
const toggleTodo = (id) => {
const todo = todos.value.find(todo => todo.id === id)
if (todo) {
todo.completed = !todo.completed
}
}
return {
todos,
filter,
addTodo,
toggleTodo
}
}
组件复用与组合式函数
创建可复用的组合式函数
组合式函数是 Composition API 的核心优势之一,它允许我们将可复用的逻辑封装起来:
// composables/useFetch.js
import { ref, watch } from 'vue'
export function useFetch(url) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchData = async () => {
if (!url.value) return
loading.value = true
error.value = null
try {
const response = await fetch(url.value)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
data.value = await response.json()
} catch (err) {
error.value = err.message
console.error('Fetch error:', err)
} finally {
loading.value = false
}
}
// 监听 URL 变化,自动重新获取数据
watch(url, fetchData, { immediate: true })
return {
data,
loading,
error,
refetch: fetchData
}
}
// 在组件中使用
import { useFetch } from '@/composables/useFetch'
import { ref } from 'vue'
export default {
setup() {
const apiUrl = ref('https://api.example.com/users')
const { data, loading, error, refetch } = useFetch(apiUrl)
return {
users: data,
loading,
error,
refetch
}
}
}
复杂业务逻辑的封装
对于复杂的业务逻辑,我们可以创建更高级的组合式函数:
// composables/useForm.js
import { ref, reactive } from 'vue'
export function useForm(initialValues = {}) {
const formData = reactive({ ...initialValues })
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} is required`
} else if (rule.minLength && value.length < rule.minLength) {
newErrors[field] = `${field} must be at least ${rule.minLength} characters`
} else if (rule.pattern && !rule.pattern.test(value)) {
newErrors[field] = `${field} format is invalid`
}
})
errors.value = newErrors
return Object.keys(newErrors).length === 0
}
const handleSubmit = async (submitHandler) => {
if (!validate()) return
isSubmitting.value = true
try {
await submitHandler(formData)
// 提交成功后重置表单
Object.keys(formData).forEach(key => {
formData[key] = ''
})
} catch (error) {
console.error('Form submission error:', error)
} finally {
isSubmitting.value = false
}
}
const reset = () => {
Object.keys(formData).forEach(key => {
formData[key] = initialValues[key] || ''
})
errors.value = {}
}
return {
formData,
errors,
isSubmitting,
validate,
handleSubmit,
reset
}
}
// 使用示例
export default {
setup() {
const {
formData,
errors,
isSubmitting,
validate,
handleSubmit
} = useForm({
name: '',
email: '',
password: ''
})
const rules = {
name: { required: true, minLength: 2 },
email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
password: { required: true, minLength: 6 }
}
const handleSave = async (data) => {
// 实际的保存逻辑
console.log('Saving data:', data)
}
const submitForm = () => {
handleSubmit(handleSave)
}
return {
formData,
errors,
isSubmitting,
submitForm
}
}
}
性能优化技巧
计算属性的优化
合理使用计算属性可以显著提升应用性能:
import { computed, ref } from 'vue'
export default {
setup() {
const items = ref([])
const filterText = ref('')
// 基础过滤计算属性
const filteredItems = computed(() => {
if (!filterText.value) return items.value
return items.value.filter(item =>
item.name.toLowerCase().includes(filterText.value.toLowerCase())
)
})
// 复杂的计算属性,使用缓存优化
const expensiveCalculation = computed(() => {
// 这个计算可能很耗时
return items.value.reduce((acc, item) => {
// 模拟复杂的计算过程
for (let i = 0; i < 1000; i++) {
acc += item.value * Math.sin(i)
}
return acc
}, 0)
})
// 避免不必要的重复计算
const optimizedItems = computed(() => {
// 只有当 items 或 filterText 改变时才重新计算
return filteredItems.value.map(item => ({
...item,
processed: item.value * 2
}))
})
return {
items,
filterText,
filteredItems,
expensiveCalculation,
optimizedItems
}
}
}
组件渲染优化
通过合理的组件结构和渲染策略来提升性能:
// components/OptimizedList.vue
import { ref, computed, defineAsyncComponent } from 'vue'
export default {
props: {
items: {
type: Array,
required: true
},
pageSize: {
type: Number,
default: 20
}
},
setup(props) {
const currentPage = ref(1)
const loading = ref(false)
// 分页计算
const paginatedItems = computed(() => {
const start = (currentPage.value - 1) * props.pageSize
const end = start + props.pageSize
return props.items.slice(start, end)
})
// 性能优化:只在需要时渲染
const shouldRenderItem = (index) => {
const startIndex = (currentPage.value - 1) * props.pageSize
const endIndex = startIndex + props.pageSize
return index >= startIndex && index < endIndex
}
// 异步组件加载优化
const AsyncComponent = defineAsyncComponent(() =>
import('./HeavyComponent.vue')
)
const loadMore = async () => {
loading.value = true
// 模拟异步加载
await new Promise(resolve => setTimeout(resolve, 1000))
currentPage.value++
loading.value = false
}
return {
currentPage,
paginatedItems,
shouldRenderItem,
AsyncComponent,
loadMore,
loading
}
}
}
防抖和节流优化
在处理用户输入或频繁触发的事件时,使用防抖和节流可以显著提升性能:
// composables/useDebounce.js
import { ref, watch } from 'vue'
export function useDebounce(value, delay = 300) {
const debouncedValue = ref(value.value)
let timeoutId = null
watch(value, (newValue) => {
if (timeoutId) {
clearTimeout(timeoutId)
}
timeoutId = setTimeout(() => {
debouncedValue.value = newValue
}, delay)
})
return debouncedValue
}
// composables/useThrottle.js
export function useThrottle(callback, delay = 1000) {
let lastCall = 0
return (...args) => {
const now = Date.now()
if (now - lastCall >= delay) {
lastCall = now
callback.apply(this, args)
}
}
}
// 使用示例
export default {
setup() {
const searchQuery = ref('')
const debouncedSearch = useDebounce(searchQuery, 500)
const handleSearch = async () => {
if (debouncedSearch.value) {
// 执行搜索逻辑
console.log('Searching for:', debouncedSearch.value)
}
}
// 节流示例
const throttledScrollHandler = useThrottle((event) => {
// 滚动处理逻辑
console.log('Scroll position:', window.scrollY)
}, 100)
return {
searchQuery,
handleSearch,
throttledScrollHandler
}
}
}
实际项目案例分析
电商商品列表组件
让我们通过一个实际的电商商品列表组件来展示 Composition API 的最佳实践:
<template>
<div class="product-list">
<!-- 搜索和过滤区域 -->
<div class="controls">
<input
v-model="searchQuery"
placeholder="搜索商品..."
@input="debouncedSearch"
/>
<select v-model="selectedCategory">
<option value="">所有分类</option>
<option v-for="category in categories" :key="category.id" :value="category.id">
{{ category.name }}
</option>
</select>
<button @click="clearFilters">清除筛选</button>
</div>
<!-- 商品列表 -->
<div class="products-grid">
<div
v-for="product in paginatedProducts"
:key="product.id"
class="product-card"
>
<img :src="product.image" :alt="product.name" />
<h3>{{ product.name }}</h3>
<p class="price">¥{{ product.price }}</p>
<button @click="addToCart(product)">加入购物车</button>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<button
@click="currentPage--"
:disabled="currentPage === 1"
>
上一页
</button>
<span>第 {{ currentPage }} 页</span>
<button
@click="currentPage++"
:disabled="currentPage === totalPages"
>
下一页
</button>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="loading">
正在加载商品...
</div>
</div>
</template>
<script>
import { ref, computed, watch } from 'vue'
import { useDebounce } from '@/composables/useDebounce'
import { usePersistentState } from '@/utils/statePersistence'
export default {
name: 'ProductList',
setup() {
// 状态管理
const products = ref([])
const loading = ref(false)
const currentPage = ref(1)
// 持久化状态
const searchQuery = usePersistentState('searchQuery', '')
const selectedCategory = usePersistentState('selectedCategory', '')
// 分页配置
const pageSize = 12
// 获取所有分类
const categories = computed(() => {
const uniqueCategories = [...new Set(products.value.map(p => p.category))]
return uniqueCategories.map(category => ({
id: category,
name: category
}))
})
// 过滤后的商品列表
const filteredProducts = computed(() => {
let result = products.value
if (searchQuery.value) {
result = result.filter(product =>
product.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
product.description.toLowerCase().includes(searchQuery.value.toLowerCase())
)
}
if (selectedCategory.value) {
result = result.filter(product =>
product.category === selectedCategory.value
)
}
return result
})
// 分页商品
const paginatedProducts = computed(() => {
const start = (currentPage.value - 1) * pageSize
const end = start + pageSize
return filteredProducts.value.slice(start, end)
})
// 总页数
const totalPages = computed(() => {
return Math.ceil(filteredProducts.value.length / pageSize)
})
// 防抖搜索
const debouncedSearch = useDebounce(searchQuery, 500)
// 加载商品数据
const loadProducts = async () => {
loading.value = true
try {
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 1000))
// 模拟商品数据
products.value = [
{ id: 1, name: 'iPhone 14', category: '手机', price: 5999, image: '/images/phone.jpg' },
{ id: 2, name: 'MacBook Pro', category: '电脑', price: 12999, image: '/images/laptop.jpg' },
{ id: 3, name: 'iPad Air', category: '平板', price: 4399, image: '/images/tablet.jpg' },
// ... 更多商品数据
]
} catch (error) {
console.error('Failed to load products:', error)
} finally {
loading.value = false
}
}
// 清除筛选条件
const clearFilters = () => {
searchQuery.value = ''
selectedCategory.value = ''
currentPage.value = 1
}
// 添加到购物车
const addToCart = (product) => {
console.log('Added to cart:', product.name)
// 实际的添加逻辑
}
// 监听分页变化
watch(currentPage, () => {
window.scrollTo({ top: 0, behavior: 'smooth' })
})
// 初始化加载
loadProducts()
return {
products,
loading,
currentPage,
searchQuery,
selectedCategory,
categories,
paginatedProducts,
totalPages,
debouncedSearch,
clearFilters,
addToCart
}
}
}
</script>
<style scoped>
.product-list {
padding: 20px;
}
.controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.product-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
text-align: center;
}
.price {
color: #e74c3c;
font-weight: bold;
font-size: 1.2em;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.loading {
text-align: center;
padding: 20px;
}
</style>
实时数据更新优化
对于需要实时更新的数据,我们可以使用更高级的优化策略:
<template>
<div class="realtime-dashboard">
<!-- 数据刷新控制 -->
<div class="dashboard-controls">
<button @click="startAutoRefresh">开始自动刷新</button>
<button @click="stopAutoRefresh">停止刷新</button>
<span>刷新间隔: {{ refreshInterval }}s</span>
</div>
<!-- 实时数据展示 -->
<div class="data-grid">
<div
v-for="item in processedData"
:key="item.id"
class="data-card"
:class="{ 'critical': item.value > 1000 }"
>
<h3>{{ item.name }}</h3>
<p class="value">{{ item.value }}</p>
<p class="timestamp">{{ formatDate(item.timestamp) }}</p>
</div>
</div>
<!-- 性能监控 -->
<div class="performance-monitor">
<p>数据更新次数: {{ updateCount }}</p>
<p>平均处理时间: {{ avgProcessingTime }}ms</p>
</div>
</div>
</template>
<script>
import { ref, computed, watchEffect } from 'vue'
export default {
setup() {
const data = ref([])
const refreshInterval = ref(5)
const autoRefresh = ref(false)
const updateCount = ref(0)
const processingStartTime = ref(null)
const processingTimes = ref([])
// 模拟实时数据
const generateRealtimeData = () => {
return Array.from({ length: 10 }, (_, i) => ({
id: Date.now() + i,
name: `指标${i + 1}`,
value: Math.floor(Math.random() * 2000),
timestamp: new Date()
}))
}
// 数据处理和优化
const processedData = computed(() => {
processingStartTime.value = performance.now()
const result = data.value.map(item => ({
...item,
// 添加一些计算字段,只在需要时计算
formattedValue: item.value.toLocaleString(),
trend: calculateTrend(item)
}))
// 记录处理时间
const processingTime = performance.now() - processingStartTime.value
processingTimes.value.push(processingTime)
// 只保留最近10次的处理时间用于计算平均值
if (processingTimes.value.length > 10) {
processingTimes.value.shift()
}
return result
})
// 计算趋势
const calculateTrend = (item) => {
// 简单的趋势计算逻辑
return item.value > 500 ? 'up' : item.value < 200 ? 'down' : 'stable'
}
// 格式化时间
const formatDate = (date) => {
return date.toLocaleTimeString()
}
// 获取平均处理时间
const avgProcessingTime = computed(() => {
if (processingTimes.value.length === 0) return 0
const sum = processingTimes.value.reduce((acc, time) => acc + time, 0)
return Math.round(sum / processingTimes.value.length)
})
// 自动刷新逻辑
let refreshTimer = null
const startAutoRefresh = () => {
autoRefresh.value = true
if (refreshTimer) clearInterval(refreshTimer)
refreshTimer = setInterval(() => {
data.value = generateRealtimeData()
updateCount.value++
}, refreshInterval.value * 1000)
}
const stopAutoRefresh = () => {
autoRefresh.value = false
if (refreshTimer) {
clearInterval(refreshTimer)
refreshTimer = null
}
}
// 监听刷新间隔变化
watchEffect(() => {
if (autoRefresh.value && refreshTimer) {
stopAutoRefresh()
startAutoRefresh()
}
})
// 初始化数据
data.value = generateRealtimeData()
return {
data,
refreshInterval,
autoRefresh,
updateCount,
processedData,
avgProcessingTime,
startAutoRefresh,
stopAutoRefresh,
formatDate
}
}
}
</script>
<style scoped>
.realtime-dashboard {
padding: 20px;
}
.dashboard-controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
align-items: center;
}
.data-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.data-card {
border: 1px solid #eee;
border-radius: 8px;
padding: 15px;
text-align: center;
transition: all 0.3s ease;
}
.data-card.critical {
border-color: #e74c3c;
background-color: #fdf2f2;
}
.value {
font-size: 1.5em;
font-weight: bold;
color: #333;
margin: 10px 0;
}
.timestamp {
font-size: 0.8em;
color: #666;
}
.performance-monitor {
background-color: #f8f9fa;
padding: 15px;
border-radius: 8px;
}
</style>
最佳实践总结
状态管理最佳实践
- 合理选择响应式数据类型:根据数据结构选择
ref或reactive - 避免深层嵌套:保持状态结构扁平化,便于维护和调试
- 使用组合式函数:将可复用逻辑封装成组合式函数
- 状态持久化:对于用户偏好等数据,考虑使用 localStorage 进行持久化
性能优化最佳实践
- 计算属性缓存:合理使用
computed进行计算结果缓存 - 避免不必要的重渲染:通过精确的状态管理减少组件更新
- 防抖节流:在高频事件处理中使用防抖和节流
- 异步加载优化:合理使用异步组件和懒加载

评论 (0)