引言
Vue 3的发布带来了革命性的变化,其中最引人注目的便是Composition API的引入。相比Vue 2中的Options API,Composition API为开发者提供了更加灵活、可组合的组件开发方式。本文将深入探讨Composition API的核心概念、使用技巧,并结合实际项目经验分享组件化开发、状态管理、性能优化等最佳实践方法。
Composition API核心概念
什么是Composition API
Composition API是Vue 3中引入的一种新的组件逻辑组织方式。它允许开发者以函数的形式组织和重用组件逻辑,打破了传统Options API中基于选项的组织方式。通过Composition API,我们可以将相关的逻辑代码组合在一起,而不是按照属性、方法等分类。
核心响应式API
Composition API的核心是响应式系统,主要包括以下关键API:
import { ref, reactive, computed, watch } from 'vue'
// Ref:创建响应式引用
const count = ref(0)
console.log(count.value) // 0
count.value = 1
console.log(count.value) // 1
// Reactive:创建响应式对象
const state = reactive({
name: 'Vue',
version: 3
})
// Computed:创建计算属性
const doubleCount = computed(() => count.value * 2)
// Watch:监听响应式数据变化
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`)
})
setup函数
setup是Composition API的入口函数,它在组件实例创建之前执行。在这个函数中,我们可以访问所有响应式API:
import { ref, reactive, computed } from 'vue'
export default {
setup() {
// 响应式数据定义
const count = ref(0)
const state = reactive({
name: 'Vue',
version: 3
})
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 方法定义
const increment = () => {
count.value++
}
// 返回给模板使用
return {
count,
state,
doubleCount,
increment
}
}
}
基础语法实践
响应式数据管理
在Composition API中,响应式数据的管理更加直观和灵活。让我们看看如何有效地管理不同类型的数据:
import { ref, reactive, toRefs } from 'vue'
export default {
setup() {
// 基本类型响应式数据
const count = ref(0)
const message = ref('Hello Vue')
// 对象类型响应式数据
const user = reactive({
name: 'John',
age: 25,
email: 'john@example.com'
})
// 数组类型响应式数据
const items = reactive([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
])
// 计算属性
const fullName = computed(() => {
return `${user.name} (${user.age})`
})
// 方法
const addItem = (name) => {
items.push({ id: Date.now(), name })
}
const updateUserAge = (age) => {
user.age = age
}
// 使用toRefs解构响应式数据
const { name, age } = toRefs(user)
return {
count,
message,
user,
items,
fullName,
addItem,
updateUserAge,
name,
age
}
}
}
生命周期钩子
Composition API提供了对应的生命周期钩子函数,让我们能够更好地控制组件的生命周期:
import { onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUpdate, onBeforeUnmount } from 'vue'
export default {
setup() {
const data = ref(null)
// 组件挂载前
onBeforeMount(() => {
console.log('组件即将挂载')
})
// 组件挂载后
onMounted(() => {
console.log('组件已挂载')
// 可以在这里进行DOM操作
data.value = 'Component mounted'
})
// 组件更新前
onBeforeUpdate(() => {
console.log('组件即将更新')
})
// 组件更新后
onUpdated(() => {
console.log('组件已更新')
})
// 组件卸载前
onBeforeUnmount(() => {
console.log('组件即将卸载')
})
// 组件卸载后
onUnmounted(() => {
console.log('组件已卸载')
})
return {
data
}
}
}
高级实践技巧
组合式函数(Composables)
组合式函数是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 double = computed(() => count.value * 2)
return {
count,
increment,
decrement,
reset,
double
}
}
// 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 () => {
loading.value = true
error.value = null
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
data.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 自动调用fetchData
watch(url, fetchData, { immediate: true })
return {
data,
loading,
error,
refetch: fetchData
}
}
// 在组件中使用
import { useCounter } from '@/composables/useCounter'
import { useFetch } from '@/composables/useFetch'
export default {
setup() {
const { count, increment, decrement, reset, double } = useCounter(10)
const { data, loading, error, refetch } = useFetch('/api/users')
return {
count,
increment,
decrement,
reset,
double,
data,
loading,
error,
refetch
}
}
}
响应式数据的深层操作
对于复杂的嵌套响应式对象,我们需要特别注意如何正确地进行数据更新:
import { ref, reactive, set, del } from 'vue'
export default {
setup() {
const state = reactive({
user: {
profile: {
name: 'John',
age: 25
}
},
items: []
})
// 正确的深层对象更新方式
const updateUserProfile = (newProfile) => {
// 使用set函数确保响应式
set(state.user, 'profile', newProfile)
}
// 或者直接替换整个对象
const updateUserName = (name) => {
state.user.profile.name = name
}
// 添加数组元素
const addItem = (item) => {
state.items.push(item)
}
// 删除数组元素
const removeItem = (index) => {
state.items.splice(index, 1)
}
return {
state,
updateUserProfile,
updateUserName,
addItem,
removeItem
}
}
}
组件化开发最佳实践
复杂组件的设计模式
在实际项目中,我们经常需要处理复杂的组件逻辑。使用Composition API可以更好地组织这些复杂逻辑:
// components/UserProfile.vue
import { ref, reactive, computed } from 'vue'
import { useFetch } from '@/composables/useFetch'
export default {
props: {
userId: {
type: Number,
required: true
}
},
setup(props) {
// 响应式状态
const isLoading = ref(false)
const isEditing = ref(false)
const editForm = reactive({
name: '',
email: '',
phone: ''
})
// 使用composable获取用户数据
const { data: user, loading, error } = useFetch(`/api/users/${props.userId}`)
// 计算属性
const hasUser = computed(() => !!user.value)
const displayName = computed(() => {
return user.value ? `${user.value.firstName} ${user.value.lastName}` : 'Unknown User'
})
// 方法
const loadUser = async () => {
if (props.userId) {
isLoading.value = true
try {
// 这里可以重新获取数据
} finally {
isLoading.value = false
}
}
}
const startEdit = () => {
if (user.value) {
Object.assign(editForm, user.value)
isEditing.value = true
}
}
const cancelEdit = () => {
isEditing.value = false
}
const saveUser = async () => {
try {
// 发送更新请求
await fetch(`/api/users/${props.userId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(editForm)
})
isEditing.value = false
// 重新加载用户数据
await loadUser()
} catch (err) {
console.error('Failed to save user:', err)
}
}
return {
isLoading,
isEditing,
editForm,
user,
loading,
error,
hasUser,
displayName,
loadUser,
startEdit,
cancelEdit,
saveUser
}
}
}
组件通信的最佳实践
在Vue 3中,组件间通信可以通过多种方式实现,使用Composition API可以更加优雅地处理这些场景:
// composables/useGlobalState.js
import { reactive } from 'vue'
// 全局状态管理
const globalState = reactive({
theme: 'light',
language: 'zh-CN',
user: null
})
export function useGlobalState() {
const setTheme = (theme) => {
globalState.theme = theme
}
const setLanguage = (language) => {
globalState.language = language
}
const setUser = (user) => {
globalState.user = user
}
return {
state: globalState,
setTheme,
setLanguage,
setUser
}
}
// 在组件中使用全局状态
import { useGlobalState } from '@/composables/useGlobalState'
export default {
setup() {
const { state, setTheme, setLanguage } = useGlobalState()
// 监听全局状态变化
watch(() => state.theme, (newTheme) => {
document.body.className = newTheme
})
return {
theme: computed(() => state.theme),
language: computed(() => state.language),
user: computed(() => state.user),
setTheme,
setLanguage
}
}
}
状态管理深度解析
组合式函数状态管理
在复杂应用中,我们可以使用组合式函数来实现轻量级的状态管理:
// composables/useStore.js
import { ref, reactive } from 'vue'
export function useStore() {
// 状态存储
const state = reactive({
todos: [],
filters: {
status: 'all',
search: ''
},
ui: {
loading: false,
error: null
}
})
// 计算属性
const filteredTodos = computed(() => {
let result = state.todos
if (state.filters.status !== 'all') {
result = result.filter(todo => todo.status === state.filters.status)
}
if (state.filters.search) {
const searchLower = state.filters.search.toLowerCase()
result = result.filter(todo =>
todo.title.toLowerCase().includes(searchLower) ||
todo.description.toLowerCase().includes(searchLower)
)
}
return result
})
// 方法
const addTodo = (todo) => {
state.todos.push({
id: Date.now(),
...todo,
createdAt: new Date()
})
}
const updateTodo = (id, updates) => {
const index = state.todos.findIndex(todo => todo.id === id)
if (index !== -1) {
Object.assign(state.todos[index], updates)
}
}
const deleteTodo = (id) => {
const index = state.todos.findIndex(todo => todo.id === id)
if (index !== -1) {
state.todos.splice(index, 1)
}
}
const setFilter = (filterKey, value) => {
state.filters[filterKey] = value
}
const setLoading = (loading) => {
state.ui.loading = loading
}
const setError = (error) => {
state.ui.error = error
}
return {
state,
filteredTodos,
addTodo,
updateTodo,
deleteTodo,
setFilter,
setLoading,
setError
}
}
// 在组件中使用
import { useStore } from '@/composables/useStore'
export default {
setup() {
const {
state,
filteredTodos,
addTodo,
updateTodo,
deleteTodo,
setFilter,
setLoading,
setError
} = useStore()
return {
todos: filteredTodos,
addTodo,
updateTodo,
deleteTodo,
setFilter,
setLoading,
setError
}
}
}
状态持久化方案
在实际项目中,我们经常需要将状态持久化到localStorage或sessionStorage中:
// composables/usePersistentState.js
import { ref, watch } from 'vue'
export function usePersistentState(key, defaultValue) {
// 从localStorage初始化状态
const state = ref(
localStorage.getItem(key)
? JSON.parse(localStorage.getItem(key))
: defaultValue
)
// 监听状态变化并保存到localStorage
watch(state, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
// 清除状态
const clear = () => {
state.value = defaultValue
localStorage.removeItem(key)
}
return {
state,
clear
}
}
// 使用示例
export default {
setup() {
const { state: preferences, clear: clearPreferences } = usePersistentState('user-preferences', {
theme: 'light',
language: 'zh-CN',
notifications: true
})
return {
preferences,
clearPreferences
}
}
}
性能优化策略
计算属性和缓存机制
合理使用计算属性可以显著提升应用性能:
import { ref, computed } from 'vue'
export default {
setup() {
const items = ref([])
const filterText = ref('')
// 复杂的计算属性,应该使用computed进行缓存
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) => {
acc += item.price * item.quantity
return acc
}, 0)
})
// 带有getter和setter的计算属性
const fullName = computed({
get: () => `${user.firstName} ${user.lastName}`,
set: (value) => {
const names = value.split(' ')
user.firstName = names[0]
user.lastName = names[1] || ''
}
})
return {
items,
filterText,
filteredItems,
expensiveCalculation,
fullName
}
}
}
监听器优化
监听器的使用需要谨慎,避免不必要的性能开销:
import { ref, watch, watchEffect } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('')
const email = ref('')
// 基础watch用法
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`)
})
// 监听多个值
watch([name, email], ([newName, newEmail], [oldName, oldEmail]) => {
console.log('Name or email changed')
})
// 深度监听
const user = ref({
profile: {
name: 'John',
settings: {
theme: 'light'
}
}
})
watch(user, (newUser) => {
console.log('User changed:', newUser)
}, { deep: true })
// 使用watchEffect,自动追踪依赖
watchEffect(() => {
// 这里会自动追踪所有响应式数据的使用
console.log(`Name: ${name.value}, Email: ${email.value}`)
})
// 防抖监听
const debouncedWatch = (source, callback, delay = 300) => {
let timeoutId
return watch(source, (...args) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => callback(...args), delay)
})
}
// 使用防抖监听搜索输入
const searchQuery = ref('')
debouncedWatch(searchQuery, (newQuery) => {
if (newQuery.length > 2) {
// 执行搜索逻辑
console.log('Searching for:', newQuery)
}
}, 500)
return {
count,
name,
email,
user,
searchQuery
}
}
}
组件懒加载和性能监控
// composables/usePerformanceMonitor.js
import { ref, onMounted, onUnmounted } from 'vue'
export function usePerformanceMonitor() {
const performanceData = ref({
loadTime: 0,
memoryUsage: 0,
renderCount: 0
})
let startTime = 0
const startMonitoring = () => {
startTime = performance.now()
// 监听组件渲染次数
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (entry.entryType === 'paint') {
performanceData.value.renderCount++
}
})
})
observer.observe({ entryTypes: ['paint'] })
}
const stopMonitoring = () => {
const endTime = performance.now()
performanceData.value.loadTime = endTime - startTime
}
onMounted(() => {
startMonitoring()
})
onUnmounted(() => {
stopMonitoring()
})
return {
performanceData
}
}
// 在组件中使用
import { usePerformanceMonitor } from '@/composables/usePerformanceMonitor'
export default {
setup() {
const { performanceData } = usePerformanceMonitor()
return {
performanceData
}
}
}
实际项目应用案例
电商商品列表组件
让我们通过一个实际的电商商品列表组件来展示Composition API的强大功能:
<template>
<div class="product-list">
<!-- 搜索和筛选 -->
<div class="controls">
<input
v-model="searchQuery"
placeholder="搜索商品..."
class="search-input"
/>
<select v-model="selectedCategory" class="category-select">
<option value="">所有分类</option>
<option v-for="category in categories" :key="category.id" :value="category.id">
{{ category.name }}
</option>
</select>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="loading">加载中...</div>
<!-- 错误处理 -->
<div v-else-if="error" class="error">
{{ error }}
</div>
<!-- 商品列表 -->
<div v-else class="products-grid">
<ProductCard
v-for="product in filteredProducts"
:key="product.id"
:product="product"
@favorite="toggleFavorite"
/>
</div>
<!-- 分页 -->
<Pagination
v-if="totalPages > 1"
:current-page="currentPage"
:total-pages="totalPages"
@page-change="changePage"
/>
</div>
</template>
<script>
import { ref, reactive, computed, watch, onMounted } from 'vue'
import ProductCard from './ProductCard.vue'
import Pagination from './Pagination.vue'
export default {
name: 'ProductList',
components: {
ProductCard,
Pagination
},
setup() {
// 响应式数据
const products = ref([])
const loading = ref(false)
const error = ref(null)
const filters = reactive({
searchQuery: '',
category: '',
sortBy: 'name',
sortOrder: 'asc'
})
const pagination = reactive({
currentPage: 1,
pageSize: 20,
total: 0
})
// 计算属性
const filteredProducts = computed(() => {
let result = [...products.value]
// 搜索过滤
if (filters.searchQuery) {
const query = filters.searchQuery.toLowerCase()
result = result.filter(product =>
product.name.toLowerCase().includes(query) ||
product.description.toLowerCase().includes(query)
)
}
// 分类过滤
if (filters.category) {
result = result.filter(product => product.categoryId === filters.category)
}
// 排序
result.sort((a, b) => {
let aValue = a[filters.sortBy]
let bValue = b[filters.sortBy]
if (typeof aValue === 'string') {
aValue = aValue.toLowerCase()
bValue = bValue.toLowerCase()
}
if (filters.sortOrder === 'asc') {
return aValue > bValue ? 1 : -1
} else {
return aValue < bValue ? 1 : -1
}
})
return result
})
const totalPages = computed(() => {
return Math.ceil(filteredProducts.value.length / pagination.pageSize)
})
const paginatedProducts = computed(() => {
const start = (pagination.currentPage - 1) * pagination.pageSize
const end = start + pagination.pageSize
return filteredProducts.value.slice(start, end)
})
// 方法
const fetchProducts = async () => {
loading.value = true
error.value = null
try {
const response = await fetch('/api/products', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
products.value = data.products
pagination.total = data.total
} catch (err) {
error.value = err.message
console.error('Failed to fetch products:', err)
} finally {
loading.value = false
}
}
const toggleFavorite = (productId) => {
const product = products.value.find(p => p.id === productId)
if (product) {
product.isFavorite = !product.isFavorite
}
}
const changePage = (page) => {
pagination.currentPage = page
// 可以在这里添加滚动到顶部的逻辑
window.scrollTo({ top: 0, behavior: 'smooth' })
}
const setSearchQuery = (query) => {
filters.searchQuery = query
pagination.currentPage = 1
}
const setCategory = (category) => {
filters.category = category
pagination.currentPage = 1
}
// 监听筛选条件变化
watch([() => filters.searchQuery, () => filters.category], () => {
pagination.currentPage = 1
})
// 组件挂载时获取数据
onMounted(() => {
fetchProducts()
})
return {
products,
loading,
error,
searchQuery: computed(() => filters.searchQuery),
selectedCategory: computed(() => filters.category),
filteredProducts: paginatedProducts,
totalPages,
currentPage: computed(() => pagination.currentPage),
// 方法
toggleFavorite,
changePage,
setSearchQuery,
setCategory
}
}
}
</script>
<style scoped>
.product-list {
padding: 20px;
}
.controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.search-input, .category-select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.loading, .error {
text-align: center;
padding: 20px;
}
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
</style>
用户管理系统
另一个复杂的实际应用案例是用户管理系统:
<template>
<div class="user-management">
<!-- 工具栏 -->
<div class="toolbar">
<button @click="showCreateForm = true" class="btn btn-primary">添加用户</button>
<input
v-model="searchQuery"
placeholder="搜索用户..."
class="search-input"
/>
</div>
<!-- 用户列表 -->
<div class="user-list">
<UserRow
v-for="user in paginatedUsers"
:key="user.id"
:user="user"
@edit="handleEdit"
@delete="handleDelete"
/>
</div>
<!-- 分页 -->
<Pagination
:current-page="currentPage"
:total-pages="totalPages"
@page-change="changePage"
/>
<!-- 用户表单模态框 -->
<UserForm
v-if="showCreateForm || editingUser"
:user="editingUser || {}"
:is-editing="!!editingUser"
@save="handleSave"
@cancel="handleCancel"
/>
</div>
</template>
<script>
import { ref, reactive, computed, watch, onMounted } from 'vue'
import UserRow from './UserRow.vue'
import UserForm from './UserForm.vue'
import Pagination from './Pagination.vue'
export default
评论 (0)