引言
Vue 3 的发布为前端开发者带来了全新的开发体验,其中最引人注目的特性之一就是 Composition API。相比传统的 Options API,Composition API 提供了更灵活、更强大的代码组织方式,特别是在处理复杂组件逻辑时表现得尤为突出。本文将深入探讨 Vue3 Composition API 的核心特性和实际应用场景,从基础的响应式数据管理到复杂的组件通信和状态管理,为 Vue3 项目提供完整的开发指导。
Vue3 Composition API 核心概念
什么是 Composition API
Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式。它允许我们通过组合函数(composable)来组织和复用组件逻辑,而不是像 Options API 那样按照选项(data、methods、computed 等)来组织代码。
核心响应式 API
Composition API 的核心是响应式系统,主要包括以下 API:
import { ref, reactive, computed, watch, watchEffect } from 'vue'
// ref 用于创建响应式数据
const count = ref(0)
console.log(count.value) // 0
// reactive 用于创建响应式对象
const state = reactive({
name: 'Vue',
version: 3
})
// computed 用于创建计算属性
const doubleCount = computed(() => count.value * 2)
// watch 监听响应式数据变化
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
响应式数据管理
Ref 和 Reactive 的区别与使用场景
在 Vue3 中,ref 和 reactive 是两种不同的响应式数据创建方式:
// ref 适用于基本类型和对象的引用
const count = ref(0)
const message = ref('Hello')
// reactive 适用于复杂对象结构
const user = reactive({
name: 'John',
age: 30,
address: {
city: 'Beijing',
street: 'Main Street'
}
})
// 在模板中使用
// <template>
// <p>{{ count }}</p>
// <p>{{ message }}</p>
// <p>{{ user.name }}</p>
// </template>
响应式数据的深层嵌套处理
对于深层嵌套的对象,Vue3 提供了更灵活的处理方式:
import { reactive, toRefs } from 'vue'
const state = reactive({
user: {
profile: {
name: 'Alice',
settings: {
theme: 'dark',
language: 'zh-CN'
}
}
}
})
// 使用 toRefs 可以将响应式对象的属性转换为 ref
const { user } = toRefs(state)
// 这样在模板中可以直接使用 user.profile.name 而不需要 user.value.profile.name
watch 和 watchEffect 的深度应用
import { watch, watchEffect } from 'vue'
const count = ref(0)
const obj = reactive({ a: 1, b: 2 })
// 基本的 watch 使用
watch(count, (newVal, oldVal) => {
console.log(`count changed: ${oldVal} -> ${newVal}`)
})
// watchEffect 会自动追踪依赖
watchEffect(() => {
console.log(`obj.a = ${obj.a}, obj.b = ${obj.b}`)
})
// 深度监听对象变化
watch(obj, (newVal, oldVal) => {
console.log('object changed:', newVal)
}, { deep: true })
// 监听多个源
watch([count, obj], ([newCount, newObj], [oldCount, oldObj]) => {
console.log(`count: ${oldCount} -> ${newCount}`)
})
组件间通信实战
Props 和 Emit 的新用法
在 Composition API 中,props 和 emit 的使用方式更加灵活:
// ChildComponent.vue
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0
}
})
const emit = defineEmits(['update-count', 'submit'])
const handleClick = () => {
emit('update-count', props.count + 1)
}
const handleSubmit = (data) => {
emit('submit', data)
}
Provide 和 Inject 的组合使用
// Parent.vue
import { provide, ref } from 'vue'
export default {
setup() {
const theme = ref('light')
const user = ref({ name: 'John', role: 'admin' })
// 提供数据给子组件
provide('theme', theme)
provide('user', user)
return {
theme,
user
}
}
}
// Child.vue
import { inject } from 'vue'
export default {
setup() {
// 注入提供的数据
const theme = inject('theme')
const user = inject('user')
return {
theme,
user
}
}
}
使用自定义 Hook 进行组件通信
// 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
}
}
// 使用自定义 Hook
import { useCounter } from '@/composables/useCounter'
export default {
setup() {
const counter = useCounter(10)
return {
...counter
}
}
}
状态管理最佳实践
基于 Composition API 的状态管理方案
// stores/userStore.js
import { reactive, readonly } from 'vue'
const state = reactive({
currentUser: null,
isLoggedIn: false,
token: ''
})
const login = (user, token) => {
state.currentUser = user
state.isLoggedIn = true
state.token = token
}
const logout = () => {
state.currentUser = null
state.isLoggedIn = false
state.token = ''
}
const updateProfile = (profile) => {
if (state.currentUser) {
Object.assign(state.currentUser, profile)
}
}
// 使用 readonly 确保状态不可直接修改
export default {
state: readonly(state),
login,
logout,
updateProfile
}
复杂状态管理的模块化设计
// stores/index.js
import { reactive } from 'vue'
// 用户状态管理
const userStore = (() => {
const state = reactive({
profile: null,
permissions: [],
preferences: {}
})
const setProfile = (profile) => {
state.profile = profile
}
const setPermissions = (permissions) => {
state.permissions = permissions
}
const updatePreferences = (preferences) => {
Object.assign(state.preferences, preferences)
}
return {
state: readonly(state),
setProfile,
setPermissions,
updatePreferences
}
})()
// 应用状态管理
const appStore = (() => {
const state = reactive({
loading: false,
error: null,
notifications: []
})
const setLoading = (loading) => {
state.loading = loading
}
const setError = (error) => {
state.error = error
}
const addNotification = (notification) => {
state.notifications.push(notification)
}
return {
state: readonly(state),
setLoading,
setError,
addNotification
}
})()
export default {
user: userStore,
app: appStore
}
状态持久化方案
// composables/usePersistentState.js
import { ref, watch } from 'vue'
export function usePersistentState(key, defaultValue) {
// 从 localStorage 恢复状态
const saved = localStorage.getItem(key)
const state = ref(saved ? JSON.parse(saved) : defaultValue)
// 监听状态变化并保存到 localStorage
watch(state, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
return state
}
// 使用示例
export default {
setup() {
const userPreferences = usePersistentState('user-preferences', {
theme: 'light',
language: 'zh-CN'
})
return {
userPreferences
}
}
}
自定义 Hook 开发
创建可复用的逻辑组合
// composables/useFetch.js
import { ref, reactive } 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
}
}
return {
data,
loading,
error,
fetchData
}
}
// 使用示例
export default {
setup() {
const { data, loading, error, fetchData } = useFetch('/api/users')
fetchData()
return {
data,
loading,
error
}
}
}
高级自定义 Hook 实现
// composables/useDebounce.js
import { ref, watch } from 'vue'
export function useDebounce(value, delay = 300) {
const debouncedValue = ref(value)
watch(
value,
(newValue) => {
const handler = setTimeout(() => {
debouncedValue.value = newValue
}, delay)
return () => clearTimeout(handler)
},
{ immediate: true }
)
return debouncedValue
}
// 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
}
}
性能优化策略
计算属性的优化使用
// 避免在模板中进行复杂的计算
export default {
setup() {
const items = ref([])
// 使用 computed 进行复杂计算
const filteredItems = computed(() => {
return items.value.filter(item => item.active)
})
const sortedItems = computed(() => {
return [...filteredItems.value].sort((a, b) => a.name.localeCompare(b.name))
})
// 对于需要缓存的复杂计算,使用 memoization
const expensiveCalculation = computed(() => {
// 模拟耗时操作
return items.value.reduce((acc, item) => {
return acc + item.value * 2
}, 0)
})
return {
filteredItems,
sortedItems,
expensiveCalculation
}
}
}
组件渲染优化
// 使用 keep-alive 缓存组件状态
// <template>
// <keep-alive>
// <component :is="currentComponent" />
// </keep-alive>
// </template>
// 使用 v-memo 进行条件渲染优化
export default {
setup() {
const items = ref([])
return {
items
}
},
// 或者在模板中使用
// <template>
// <div v-for="item in items" :key="item.id" v-memo="[item.id]">
// {{ item.name }}
// </div>
// </template>
}
实际项目应用案例
完整的购物车组件实现
<template>
<div class="shopping-cart">
<h2>购物车 ({{ cartItems.length }})</h2>
<div v-if="loading">加载中...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else>
<div
v-for="item in cartItems"
:key="item.id"
class="cart-item"
>
<span>{{ item.name }}</span>
<span>数量: {{ item.quantity }}</span>
<span>小计: ¥{{ (item.price * item.quantity).toFixed(2) }}</span>
<button @click="removeItem(item.id)">删除</button>
</div>
<div class="cart-total">
<h3>总计: ¥{{ totalPrice.toFixed(2) }}</h3>
<button @click="checkout">结算</button>
</div>
</div>
</div>
</template>
<script>
import { ref, computed } from 'vue'
import { useFetch } from '@/composables/useFetch'
export default {
name: 'ShoppingCart',
setup() {
const cartItems = ref([])
const loading = ref(false)
const error = ref(null)
// 获取购物车数据
const fetchCart = async () => {
try {
loading.value = true
const response = await fetch('/api/cart')
if (!response.ok) throw new Error('获取购物车失败')
cartItems.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 计算总价
const totalPrice = computed(() => {
return cartItems.value.reduce((total, item) => {
return total + (item.price * item.quantity)
}, 0)
})
// 删除商品
const removeItem = async (itemId) => {
try {
await fetch(`/api/cart/${itemId}`, { method: 'DELETE' })
cartItems.value = cartItems.value.filter(item => item.id !== itemId)
} catch (err) {
error.value = err.message
}
}
// 结算
const checkout = async () => {
try {
await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: cartItems.value })
})
alert('结算成功!')
cartItems.value = []
} catch (err) {
error.value = err.message
}
}
// 初始化数据
fetchCart()
return {
cartItems,
loading,
error,
totalPrice,
removeItem,
checkout
}
}
}
</script>
<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;
padding-top: 20px;
border-top: 2px solid #eee;
}
</style>
多级组件通信实战
<!-- App.vue -->
<template>
<div id="app">
<Header :user="currentUser" @logout="handleLogout" />
<MainContent :theme="currentTheme" />
<Footer :version="appVersion" />
</div>
</template>
<script>
import { ref, provide } from 'vue'
import Header from './components/Header.vue'
import MainContent from './components/MainContent.vue'
import Footer from './components/Footer.vue'
export default {
name: 'App',
components: {
Header,
MainContent,
Footer
},
setup() {
const currentUser = ref(null)
const currentTheme = ref('light')
const appVersion = ref('1.0.0')
// 提供全局状态
provide('currentUser', currentUser)
provide('currentTheme', currentTheme)
provide('appVersion', appVersion)
const handleLogout = () => {
currentUser.value = null
}
return {
currentUser,
currentTheme,
appVersion,
handleLogout
}
}
}
</script>
最佳实践总结
代码组织原则
- 单一职责原则:每个自定义 Hook 应该只负责一个特定的功能
- 可复用性:设计时要考虑组件间的通用逻辑复用
- 类型安全:使用 TypeScript 可以提供更好的开发体验
// 类型安全的自定义 Hook 示例
import { ref, computed } from 'vue'
export function useTypedCounter(initialValue = 0) {
const count = ref<number>(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
}
}
调试和测试
// 测试用的自定义 Hook
import { ref } from 'vue'
export function useTestableCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
// 可以在测试中直接访问
const getCount = () => count.value
return {
count,
increment,
decrement,
getCount
}
}
结论
Vue3 Composition API 为前端开发带来了革命性的变化,它不仅提供了更灵活的代码组织方式,还让组件逻辑的复用变得更加简单。通过本文的详细介绍,我们看到了从基础响应式数据管理到复杂状态管理的完整应用方案。
在实际项目中,合理运用 Composition API 可以显著提升代码的可维护性和可读性。关键是要理解其核心概念,掌握最佳实践,并根据具体需求选择合适的模式。无论是简单的组件通信还是复杂的全局状态管理,Composition API 都能提供强大的支持。
随着 Vue3 生态系统的不断完善,我们有理由相信 Composition API 将成为现代前端开发的标准工具集,为开发者带来更加优雅和高效的开发体验。

评论 (0)