前言
Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。相比于传统的 Options API,Composition API 提供了更灵活、更强大的组件开发方式。本文将深入探讨 Vue 3 Composition API 的核心特性,并通过实际案例展示如何在项目中应用这些技术。
Vue 3 Composition API 核心概念
什么是 Composition API
Composition API 是 Vue 3 中引入的一种新的组件开发模式,它允许我们使用函数来组织和复用组件逻辑。与传统的 Options API(data、methods、computed、watch 等选项)不同,Composition API 将组件的逻辑按照功能进行分组,使得代码更加模块化和可重用。
Composition API 的优势
- 更好的逻辑复用:通过组合函数实现跨组件的逻辑共享
- 更清晰的代码结构:将相关逻辑组织在一起,提高可读性
- 更灵活的开发方式:可以自由地组织和重组组件逻辑
- 更好的 TypeScript 支持:类型推断更加准确
响应式数据管理
reactive 和 ref 的基本使用
在 Composition API 中,响应式数据主要通过 reactive 和 ref 两个函数来创建。
import { reactive, ref } from 'vue'
// 使用 ref 创建响应式数据
const count = ref(0)
const message = ref('Hello Vue 3')
// 使用 reactive 创建响应式对象
const state = reactive({
name: 'Vue',
version: 3,
isAwesome: true
})
// 在模板中使用
// {{ count }} {{ message }} {{ state.name }}
ref 的深层应用
import { ref, watch } from 'vue'
export default {
setup() {
const user = ref({
profile: {
name: 'John',
age: 25
}
})
// 监听嵌套属性变化
watch(() => user.value.profile.name, (newName, oldName) => {
console.log(`Name changed from ${oldName} to ${newName}`)
})
const increment = () => {
user.value.profile.age++
}
return {
user,
increment
}
}
}
computed 的高级用法
import { ref, computed } from 'vue'
export default {
setup() {
const firstName = ref('John')
const lastName = ref('Doe')
// 基础计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 带有 getter 和 setter 的计算属性
const reversedName = computed({
get: () => {
return fullName.value.split(' ').reverse().join(' ')
},
set: (newValue) => {
const names = newValue.split(' ')
firstName.value = names[0]
lastName.value = names[1] || ''
}
})
return {
firstName,
lastName,
fullName,
reversedName
}
}
}
组件间通信机制
Props 传递数据
// 父组件
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
export default {
setup() {
const message = ref('Hello from parent')
const count = ref(10)
return {
message,
count
}
}
}
<!-- 父组件模板 -->
<template>
<div>
<ChildComponent
:message="message"
:count="count"
@child-event="handleChildEvent"
/>
</div>
</template>
<!-- 子组件 -->
<script setup>
// 定义 props
const props = defineProps({
message: {
type: String,
required: true
},
count: {
type: Number,
default: 0
}
})
// 定义 emits
const emit = defineEmits(['child-event'])
// 处理事件
const handleClick = () => {
emit('child-event', 'Data from child')
}
</script>
<template>
<div>
<p>{{ message }}</p>
<p>Count: {{ count }}</p>
<button @click="handleClick">Send Event</button>
</div>
</template>
emit 事件通信
<script setup>
const emit = defineEmits(['update:modelValue', 'custom-event'])
const handleInput = (value) => {
// 触发更新事件
emit('update:modelValue', value)
}
const handleCustomEvent = () => {
emit('custom-event', {
timestamp: Date.now(),
data: 'some data'
})
}
</script>
provide 和 inject 的使用
// 父组件
import { provide, ref } from 'vue'
export default {
setup() {
const theme = ref('dark')
const user = ref({ name: 'John', role: 'admin' })
// 提供数据
provide('theme', theme)
provide('user', user)
const toggleTheme = () => {
theme.value = theme.value === 'dark' ? 'light' : 'dark'
}
return {
toggleTheme
}
}
}
<!-- 子组件 -->
<script setup>
import { inject } from 'vue'
// 注入数据
const theme = inject('theme')
const user = inject('user')
// 使用注入的数据
console.log(`Current theme: ${theme.value}`)
console.log(`User: ${user.value.name}`)
</script>
<template>
<div :class="theme">
<p>User: {{ user.name }}</p>
</div>
</template>
状态管理最佳实践
使用 Composition API 实现简单状态管理
// store/userStore.js
import { ref, readonly } from 'vue'
const currentUser = ref(null)
const isLoggedIn = ref(false)
export function useUserStore() {
const login = (userData) => {
currentUser.value = userData
isLoggedIn.value = true
}
const logout = () => {
currentUser.value = null
isLoggedIn.value = false
}
const updateProfile = (newData) => {
if (currentUser.value) {
currentUser.value = { ...currentUser.value, ...newData }
}
}
return {
// 只读状态
currentUser: readonly(currentUser),
isLoggedIn: readonly(isLoggedIn),
// 方法
login,
logout,
updateProfile
}
}
<!-- 登录组件 -->
<script setup>
import { useUserStore } from '@/store/userStore'
const { login, isLoggedIn } = useUserStore()
const handleLogin = () => {
login({
name: 'John Doe',
email: 'john@example.com',
role: 'user'
})
}
</script>
<template>
<div>
<button v-if="!isLoggedIn" @click="handleLogin">Login</button>
<div v-else>Logged in as {{ currentUser.name }}</div>
</div>
</template>
复杂状态管理的组合函数
// composables/useAsyncState.js
import { ref, computed } from 'vue'
export function useAsyncState(asyncFunction, initialValue = null) {
const loading = ref(false)
const error = ref(null)
const data = ref(initialValue)
const execute = async (...args) => {
loading.value = true
error.value = null
try {
const result = await asyncFunction(...args)
data.value = result
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
const reset = () => {
data.value = initialValue
error.value = null
loading.value = false
}
return {
data: computed(() => data.value),
loading: computed(() => loading.value),
error: computed(() => error.value),
execute,
reset
}
}
<!-- 数据获取组件 -->
<script setup>
import { useAsyncState } from '@/composables/useAsyncState'
const { data, loading, error, execute } = useAsyncState(fetchUserData, [])
const fetchUser = async (userId) => {
await execute(`api/users/${userId}`)
}
// 组件加载时获取数据
execute('api/users/123')
</script>
<template>
<div>
<div v-if="loading">Loading...</div>
<div v-else-if="error">{{ error.message }}</div>
<div v-else>{{ data }}</div>
</div>
</template>
高级响应式特性
watch 的高级用法
import { ref, watch, watchEffect } from 'vue'
export default {
setup() {
const count = ref(0)
const firstName = ref('John')
const lastName = ref('Doe')
// 基础监听器
watch(count, (newVal, oldVal) => {
console.log(`Count changed from ${oldVal} to ${newVal}`)
})
// 监听多个源
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log(`Name changed from ${oldFirst} ${oldLast} to ${newFirst} ${newLast}`)
})
// 深度监听
const user = ref({
profile: {
name: 'John',
settings: {
theme: 'dark'
}
}
})
watch(user, (newUser) => {
console.log('User changed:', newUser)
}, { deep: true })
// 立即执行监听
watch(count, (newVal) => {
console.log(`Immediate watch: ${newVal}`)
}, { immediate: true })
// watchEffect
const watchEffectExample = watchEffect(() => {
console.log(`Count is now: ${count.value}`)
})
return {
count,
firstName,
lastName
}
}
}
watchEffect 和 watch 的区别
// watchEffect 会立即执行,并自动追踪依赖
const watchEffectExample = watchEffect(() => {
// 这里会立即执行一次
console.log(`Current count: ${count.value}`)
console.log(`Current name: ${firstName.value}`)
})
// watch 需要显式指定要监听的源
const watchExample = watch(count, (newVal) => {
console.log(`Count changed to: ${newVal}`)
})
组件逻辑复用
自定义组合函数
// composables/useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
const storedValue = localStorage.getItem(key)
const value = ref(storedValue ? JSON.parse(storedValue) : defaultValue)
watch(value, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
return value
}
<!-- 使用本地存储的组件 -->
<script setup>
import { useLocalStorage } from '@/composables/useLocalStorage'
const theme = useLocalStorage('app-theme', 'light')
const userPreferences = useLocalStorage('user-preferences', {
notifications: true,
language: 'en'
})
</script>
<template>
<div :class="theme">
<p>Current theme: {{ theme }}</p>
<p>User preferences: {{ userPreferences }}</p>
</div>
</template>
复杂逻辑的模块化
// composables/useFormValidation.js
import { ref, computed } from 'vue'
export function useFormValidation(initialData) {
const formData = ref({ ...initialData })
const errors = ref({})
const isValid = computed(() => {
return Object.keys(errors.value).length === 0
})
const validateField = (fieldName, value) => {
// 简单的验证规则
switch (fieldName) {
case 'email':
if (!value.includes('@')) {
errors.value.email = 'Invalid email format'
} else {
delete errors.value.email
}
break
case 'password':
if (value.length < 6) {
errors.value.password = 'Password must be at least 6 characters'
} else {
delete errors.value.password
}
break
}
}
const validateForm = () => {
Object.keys(formData.value).forEach(key => {
validateField(key, formData.value[key])
})
return isValid.value
}
const setFieldValue = (field, value) => {
formData.value[field] = value
validateField(field, value)
}
return {
formData,
errors,
isValid,
validateForm,
setFieldValue
}
}
<!-- 表单组件 -->
<script setup>
import { useFormValidation } from '@/composables/useFormValidation'
const initialData = {
email: '',
password: ''
}
const {
formData,
errors,
isValid,
validateForm,
setFieldValue
} = useFormValidation(initialData)
const handleSubmit = () => {
if (validateForm()) {
console.log('Form submitted:', formData.value)
} else {
console.log('Form has errors')
}
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<input
v-model="formData.email"
type="email"
placeholder="Email"
@blur="validateForm"
/>
<div v-if="errors.email" class="error">{{ errors.email }}</div>
<input
v-model="formData.password"
type="password"
placeholder="Password"
@blur="validateForm"
/>
<div v-if="errors.password" class="error">{{ errors.password }}</div>
<button type="submit" :disabled="!isValid">Submit</button>
</form>
</template>
性能优化技巧
使用 computed 缓存计算结果
import { ref, computed } from 'vue'
export default {
setup() {
const items = ref([])
// 复杂的计算属性,使用 computed 进行缓存
const expensiveCalculation = computed(() => {
console.log('Computing expensive calculation...')
return items.value.reduce((sum, item) => sum + item.value, 0)
})
const filteredItems = computed(() => {
return items.value.filter(item => item.active)
})
return {
items,
expensiveCalculation,
filteredItems
}
}
}
合理使用 watch 和 watchEffect
// 避免不必要的监听
export default {
setup() {
const count = ref(0)
const name = ref('John')
// 仅监听需要的值
watch(count, (newVal) => {
console.log(`Count changed: ${newVal}`)
})
// 避免在 watch 中进行复杂的计算
watch(name, (newName) => {
// 简单的副作用操作
document.title = newName
})
return {
count,
name
}
}
}
实际项目应用案例
完整的购物车功能实现
// composables/useShoppingCart.js
import { ref, computed } from 'vue'
export function useShoppingCart() {
const items = ref([])
const cartTotal = computed(() => {
return items.value.reduce((total, item) => {
return total + (item.price * item.quantity)
}, 0)
})
const itemCount = computed(() => {
return items.value.reduce((count, item) => count + item.quantity, 0)
})
const addToCart = (product) => {
const existingItem = items.value.find(item => item.id === product.id)
if (existingItem) {
existingItem.quantity += 1
} else {
items.value.push({
id: product.id,
name: product.name,
price: product.price,
quantity: 1
})
}
}
const removeFromCart = (productId) => {
items.value = items.value.filter(item => item.id !== productId)
}
const updateQuantity = (productId, newQuantity) => {
if (newQuantity <= 0) {
removeFromCart(productId)
return
}
const item = items.value.find(item => item.id === productId)
if (item) {
item.quantity = newQuantity
}
}
const clearCart = () => {
items.value = []
}
return {
items: computed(() => items.value),
cartTotal,
itemCount,
addToCart,
removeFromCart,
updateQuantity,
clearCart
}
}
<!-- 购物车组件 -->
<script setup>
import { useShoppingCart } from '@/composables/useShoppingCart'
const {
items,
cartTotal,
itemCount,
addToCart,
removeFromCart,
updateQuantity
} = useShoppingCart()
// 模拟产品数据
const products = [
{ id: 1, name: 'Product A', price: 29.99 },
{ id: 2, name: 'Product B', price: 39.99 },
{ id: 3, name: 'Product C', price: 49.99 }
]
const handleAddToCart = (product) => {
addToCart(product)
}
</script>
<template>
<div class="shopping-cart">
<h2>Shopping Cart ({{ itemCount }} items)</h2>
<div v-if="items.length === 0" class="empty-cart">
Your cart is empty
</div>
<div v-else>
<div
v-for="item in items"
:key="item.id"
class="cart-item"
>
<span>{{ item.name }}</span>
<span>${{ item.price }}</span>
<input
type="number"
:value="item.quantity"
@input="updateQuantity(item.id, $event.target.value)"
min="1"
/>
<button @click="removeFromCart(item.id)">Remove</button>
</div>
<div class="cart-total">
Total: ${{ cartTotal.toFixed(2) }}
</div>
</div>
<div class="products">
<h3>Products</h3>
<div
v-for="product in products"
:key="product.id"
class="product"
>
<span>{{ product.name }}</span>
<span>${{ product.price }}</span>
<button @click="handleAddToCart(product)">Add to Cart</button>
</div>
</div>
</div>
</template>
<style scoped>
.shopping-cart {
padding: 20px;
}
.cart-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.cart-total {
margin-top: 20px;
font-size: 1.2em;
font-weight: bold;
}
.products {
margin-top: 30px;
}
.product {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
</style>
最佳实践总结
代码组织原则
- 按功能分组:将相关逻辑组织在一起
- 合理使用组合函数:提取可复用的逻辑
- 保持响应式数据的简洁性:避免过度嵌套
- 明确的数据流向:确保状态变更清晰可追踪
性能优化建议
- 合理使用 computed:利用缓存机制
- 避免不必要的监听:只监听需要的值
- 及时清理副作用:在组件销毁时清理监听器
- 批量更新数据:减少不必要的重新渲染
开发工具和调试技巧
// 使用 Vue DevTools 进行调试
import { ref, watch } from 'vue'
export default {
setup() {
const count = ref(0)
// 在开发环境中添加调试信息
if (process.env.NODE_ENV === 'development') {
watch(count, (newVal, oldVal) => {
console.log(`[DEBUG] Count changed: ${oldVal} -> ${newVal}`)
})
}
return { count }
}
}
结语
Vue 3 的 Composition API 为前端开发带来了更灵活、更强大的组件开发方式。通过合理使用 ref、reactive、computed、watch 等核心特性,我们可以构建出更加模块化、可复用的组件逻辑。
在实际项目中,建议:
- 从简单的组合函数开始,逐步增加复杂度
- 充分利用 TypeScript 提供的类型安全
- 建立统一的组合函数命名规范
- 定期重构和优化现有代码
随着对 Composition API 理解的深入,开发者将能够构建出更加优雅、高效的 Vue 应用程序。记住,好的代码不仅功能完善,更要易于维护和扩展。通过持续实践和学习,我们可以在 Vue 3 的世界中创造出更多优秀的应用。
本文提供的示例和最佳实践希望能够帮助读者更好地理解和应用 Vue 3 Composition API,在实际开发中发挥其最大价值。

评论 (0)