引言
Vue 3 的发布带来了全新的 Composition API,这一创新性的 API 设计为开发者提供了更加灵活和强大的组件开发方式。相比于传统的 Options API,Composition API 将逻辑组织得更加自然,使得代码复用和维护变得更加容易。本文将深入探讨 Vue 3 Composition API 的各个方面,从基础概念到高级实践,涵盖组件通信、响应式数据管理、组合式函数封装等核心内容。
Vue 3 Composition API 基础概念
什么是 Composition API
Composition API 是 Vue 3 中引入的一种新的组件开发方式,它允许我们使用函数来组织和复用逻辑代码。与传统的 Options API(如 data、methods、computed 等)不同,Composition API 将相关的逻辑组合在一起,而不是按照属性类型分组。
核心响应式函数
Composition API 的核心是几个基础的响应式函数:
import { ref, reactive, computed, watch } from 'vue'
// ref 用于创建响应式变量
const count = ref(0)
console.log(count.value) // 0
// reactive 用于创建响应式对象
const state = reactive({
name: 'Vue',
version: '3.0'
})
// computed 用于创建计算属性
const doubleCount = computed(() => count.value * 2)
// watch 用于监听响应式数据变化
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
组件通信实战
父子组件通信
在 Vue 3 中,父子组件通信可以通过 props 和 emit 来实现。使用 Composition API,我们可以更优雅地处理这些通信:
<!-- Parent.vue -->
<template>
<div>
<h2>Parent Component</h2>
<Child
:message="parentMessage"
:user-info="userInfo"
@child-event="handleChildEvent"
/>
<p>Received from child: {{ childResponse }}</p>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import Child from './Child.vue'
const parentMessage = ref('Hello from Parent')
const userInfo = reactive({
name: 'John',
age: 25
})
const childResponse = ref('')
const handleChildEvent = (data) => {
childResponse.value = data
}
</script>
<!-- Child.vue -->
<template>
<div>
<h3>Child Component</h3>
<p>{{ message }}</p>
<p>User: {{ userInfo.name }}, Age: {{ userInfo.age }}</p>
<button @click="sendToParent">Send to Parent</button>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
// 定义 props
const props = defineProps({
message: {
type: String,
default: ''
},
userInfo: {
type: Object,
required: true
}
})
// 定义 emits
const emit = defineEmits(['child-event'])
const sendToParent = () => {
emit('child-event', `Response from child: ${props.message}`)
}
</script>
兄弟组件通信
兄弟组件之间的通信可以通过 EventBus 或者更现代的 Pinia 状态管理来实现。这里我们展示使用全局状态管理的方式:
<!-- ComponentA.vue -->
<template>
<div>
<h3>Component A</h3>
<p>Shared Data: {{ sharedData }}</p>
<button @click="updateSharedData">Update Data</button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useSharedStore } from '@/stores/shared'
const store = useSharedStore()
const sharedData = ref(store.data)
watch(() => store.data, (newVal) => {
sharedData.value = newVal
})
const updateSharedData = () => {
store.updateData('Updated from Component A')
}
</script>
响应式数据管理
使用 ref 和 reactive
在 Composition API 中,ref 和 reactive 是两个核心的响应式函数:
import { ref, reactive } from 'vue'
// 使用 ref 创建响应式变量
const count = ref(0)
const name = ref('Vue')
const isVisible = ref(false)
// 使用 reactive 创建响应式对象
const user = reactive({
id: 1,
name: 'John',
email: 'john@example.com',
profile: {
avatar: '',
bio: ''
}
})
// 修改值的方式
count.value = 10
name.value = 'Vue 3'
user.name = 'Jane' // 对于 reactive 对象,直接修改属性即可
user.profile.avatar = 'avatar.jpg'
计算属性和监听器
import { ref, computed, watch, watchEffect } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// 计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 带 setter 的计算属性
const reversedName = computed({
get: () => {
return firstName.value.split('').reverse().join('')
},
set: (value) => {
const names = value.split(' ')
firstName.value = names[0]
lastName.value = names[1]
}
})
// 监听器
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
// 监听单个值
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
// 监听多个值
watch([firstName, lastName], ([newFirstName, newLastName], [oldFirstName, oldLastName]) => {
console.log(`Name changed from ${oldFirstName} ${oldLastName} to ${newFirstName} ${newLastName}`)
})
// watchEffect 自动追踪依赖
watchEffect(() => {
console.log(`Full name is: ${fullName.value}`)
})
组合式函数封装
创建可复用的组合式函数
组合式函数是 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 doubleCount = computed(() => count.value * 2)
return {
count,
increment,
decrement,
reset,
doubleCount
}
}
// composables/useApi.js
import { ref, reactive } from 'vue'
export function useApi() {
const loading = ref(false)
const error = ref(null)
const data = ref(null)
const fetchData = async (url) => {
try {
loading.value = true
error.value = null
const response = await fetch(url)
data.value = await response.json()
} catch (err) {
error.value = err.message
console.error('API Error:', err)
} finally {
loading.value = false
}
}
return {
loading,
error,
data,
fetchData
}
}
<!-- 使用组合式函数的组件 -->
<template>
<div>
<h2>Counter Component</h2>
<p>Count: {{ count }}</p>
<p>Double: {{ doubleCount }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">Reset</button>
</div>
</template>
<script setup>
import { useCounter } from '@/composables/useCounter'
const { count, increment, decrement, reset, doubleCount } = useCounter(0)
</script>
高级组合式函数示例
// 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
}
// composables/useWindowResize.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useWindowResize() {
const windowWidth = ref(window.innerWidth)
const windowHeight = ref(window.innerHeight)
const handleResize = () => {
windowWidth.value = window.innerWidth
windowHeight.value = window.innerHeight
}
onMounted(() => {
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
return {
windowWidth,
windowHeight
}
}
状态管理最佳实践
使用 Pinia 进行状态管理
虽然 Composition API 提供了强大的响应式能力,但在复杂应用中,状态管理仍然是一个挑战。Pinia 是 Vue 3 推荐的状态管理库:
// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
const user = ref(null)
const isLoggedIn = computed(() => !!user.value)
const login = (userData) => {
user.value = userData
}
const logout = () => {
user.value = null
}
const updateProfile = (profileData) => {
if (user.value) {
user.value.profile = { ...user.value.profile, ...profileData }
}
}
return {
user,
isLoggedIn,
login,
logout,
updateProfile
}
})
<!-- UserComponent.vue -->
<template>
<div>
<div v-if="isLoggedIn">
<h2>Welcome, {{ user?.name }}!</h2>
<button @click="logout">Logout</button>
</div>
<div v-else>
<h2>Please login</h2>
<button @click="login">Login</button>
</div>
</div>
</template>
<script setup>
import { useUserStore } from '@/stores/user'
import { onMounted } from 'vue'
const store = useUserStore()
const { user, isLoggedIn, login, logout } = store
const login = () => {
store.login({
id: 1,
name: 'John Doe',
email: 'john@example.com',
profile: {
avatar: 'avatar.jpg',
bio: 'Vue developer'
}
})
}
</script>
多状态管理模块
// stores/index.js
import { defineStore } from 'pinia'
export const useMainStore = defineStore('main', () => {
const theme = ref('light')
const language = ref('en')
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
return {
theme,
language,
toggleTheme
}
})
// stores/products.js
import { defineStore } from 'pinia'
export const useProductStore = defineStore('products', () => {
const products = ref([])
const loading = ref(false)
const fetchProducts = async () => {
try {
loading.value = true
const response = await fetch('/api/products')
products.value = await response.json()
} catch (error) {
console.error('Failed to fetch products:', error)
} finally {
loading.value = false
}
}
const addProduct = (product) => {
products.value.push(product)
}
return {
products,
loading,
fetchProducts,
addProduct
}
})
高级特性与最佳实践
异步数据处理
<script setup>
import { ref, onMounted } from 'vue'
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchData = async () => {
try {
loading.value = true
error.value = null
const response = await fetch('/api/data')
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
}
}
onMounted(() => {
fetchData()
})
</script>
条件渲染与动态组件
<script setup>
import { ref, computed } from 'vue'
const currentComponent = ref('ComponentA')
const showComponent = ref(true)
const components = {
ComponentA: () => import('./ComponentA.vue'),
ComponentB: () => import('./ComponentB.vue')
}
const dynamicComponent = computed(() => {
return showComponent.value ? components[currentComponent.value] : null
})
</script>
<template>
<div>
<button @click="currentComponent = 'ComponentA'">Show A</button>
<button @click="currentComponent = 'ComponentB'">Show B</button>
<button @click="showComponent = !showComponent">Toggle</button>
<component :is="dynamicComponent" v-if="dynamicComponent" />
</div>
</template>
性能优化技巧
// 使用 useMemo 避免不必要的计算
import { computed, ref } from 'vue'
const items = ref([])
const searchTerm = ref('')
// 避免在每次渲染时都重新计算过滤结果
const filteredItems = computed(() => {
if (!searchTerm.value) return items.value
return items.value.filter(item =>
item.name.toLowerCase().includes(searchTerm.value.toLowerCase())
)
})
// 使用防抖函数优化搜索
import { debounce } from 'lodash'
const debouncedSearch = debounce((term) => {
searchTerm.value = term
}, 300)
实际项目应用案例
完整的购物车功能实现
<!-- ShoppingCart.vue -->
<template>
<div class="shopping-cart">
<h2>Shopping Cart</h2>
<div v-if="loading">Loading...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else>
<div
v-for="item in cartItems"
:key="item.id"
class="cart-item"
>
<h3>{{ item.name }}</h3>
<p>Price: ${{ item.price }}</p>
<p>Quantity: {{ item.quantity }}</p>
<button @click="removeItem(item.id)">Remove</button>
</div>
<div class="cart-summary">
<h3>Total: ${{ totalAmount }}</h3>
<button @click="checkout">Checkout</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useCartStore } from '@/stores/cart'
const store = useCartStore()
const loading = ref(false)
const error = ref(null)
const cartItems = computed(() => store.items)
const totalAmount = computed(() => {
return store.items.reduce((total, item) => {
return total + (item.price * item.quantity)
}, 0)
})
const removeItem = (id) => {
store.removeItem(id)
}
const checkout = () => {
// 实现结账逻辑
console.log('Checkout:', cartItems.value)
store.clearCart()
}
onMounted(() => {
// 初始化购物车数据
store.loadCart()
})
</script>
<style scoped>
.shopping-cart {
padding: 20px;
}
.cart-item {
border: 1px solid #ccc;
margin: 10px 0;
padding: 15px;
}
.cart-summary {
margin-top: 20px;
padding: 20px;
border-top: 2px solid #000;
}
</style>
// stores/cart.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCartStore = defineStore('cart', () => {
const items = ref([])
const loading = ref(false)
const totalItems = computed(() => items.value.reduce((total, item) => total + item.quantity, 0))
const loadCart = async () => {
try {
loading.value = true
const response = await fetch('/api/cart')
items.value = await response.json()
} catch (error) {
console.error('Failed to load cart:', error)
} finally {
loading.value = false
}
}
const addItem = (item) => {
const existingItem = items.value.find(i => i.id === item.id)
if (existingItem) {
existingItem.quantity += item.quantity
} else {
items.value.push(item)
}
}
const removeItem = (id) => {
items.value = items.value.filter(item => item.id !== id)
}
const updateQuantity = (id, quantity) => {
const item = items.value.find(i => i.id === id)
if (item) {
item.quantity = quantity
}
}
const clearCart = () => {
items.value = []
}
return {
items,
loading,
totalItems,
loadCart,
addItem,
removeItem,
updateQuantity,
clearCart
}
})
总结
Vue 3 的 Composition API 为前端开发带来了革命性的变化,它不仅提供了更灵活的代码组织方式,还让逻辑复用变得更加简单。通过本文的介绍,我们深入了解了:
- 基础概念:理解 ref、reactive、computed、watch 等核心响应式函数的使用
- 组件通信:掌握父子组件、兄弟组件之间的通信机制
- 状态管理:学习如何使用 Pinia 进行复杂应用的状态管理
- 组合式函数:学会封装可复用的逻辑代码
- 最佳实践:掌握性能优化和实际项目应用技巧
随着 Vue 3 的普及,Composition API 已经成为现代 Vue 开发的标准方式。它不仅提高了代码的可维护性,还使得团队协作变得更加高效。通过合理使用 Composition API,我们可以构建出更加健壮、可扩展的 Vue 应用程序。
在实际开发中,建议根据项目复杂度选择合适的模式:
- 简单组件可以继续使用 Options API
- 复杂逻辑需要复用时,优先考虑 Composition API
- 团队协作时,建立统一的组合式函数规范
Vue 3 的 Composition API 为我们打开了新的可能性,让我们能够以更加优雅的方式构建现代前端应用。

评论 (0)