前言
Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。作为 Vue 2 和 Vue 3 过渡时期的重要技术升级,Composition API 不仅解决了 Options API 在大型项目中的局限性,还为开发者提供了更加灵活、可复用的状态管理方案。
本文将深入探讨 Vue 3 Composition API 的核心概念和使用技巧,通过实际项目案例展示如何构建高效的状态管理方案。我们将涵盖响应式数据处理、组合函数复用、生命周期管理等关键知识点,帮助开发者提升 Vue 开发效率,构建更加优雅的前端应用。
一、Vue 3 Composition API 核心概念
1.1 什么是 Composition API
Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式。与 Vue 2 中的 Options API(data、methods、computed、watch 等选项)不同,Composition API 允许我们通过组合函数的方式组织和复用组件逻辑。
在 Vue 2 中,组件逻辑分散在不同的选项中,导致大型组件难以维护。而 Composition API 将相关的逻辑集中在一起,使得代码更加清晰和易于理解。
1.2 响应式系统的核心方法
Composition API 的核心是响应式系统,主要包括以下几个重要方法:
reactive() 和 ref()
import { reactive, ref } 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,
features: ['Composition API', 'Performance']
})
computed() 和 watch()
import { ref, computed, watch } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 监听响应式数据变化
watch(fullName, (newVal, oldVal) => {
console.log(`Full name changed from ${oldVal} to ${newVal}`)
})
1.3 setup() 函数
setup() 是 Composition API 的入口函数,它在组件实例创建之前执行,接收 props 和 context 参数:
import { ref, reactive } from 'vue'
export default {
props: ['title'],
setup(props, context) {
// 在这里定义响应式数据和逻辑
const count = ref(0)
const state = reactive({
message: 'Hello Vue 3'
})
// 返回给模板使用的数据和方法
return {
count,
state
}
}
}
二、基础状态管理实践
2.1 基本响应式数据处理
让我们从一个简单的计数器组件开始,展示如何使用 Composition API 进行基础的状态管理:
<template>
<div class="counter">
<h2>Counter: {{ count }}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="reset">Reset</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'Counter',
setup() {
const count = ref(0)
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
const reset = () => {
count.value = 0
}
return {
count,
increment,
decrement,
reset
}
}
}
</script>
2.2 复杂对象的状态管理
对于更复杂的状态,我们通常需要使用 reactive() 来处理对象:
<template>
<div class="user-profile">
<h2>{{ user.name }}</h2>
<p>Email: {{ user.email }}</p>
<p>Age: {{ user.age }}</p>
<button @click="updateProfile">Update Profile</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'UserProfile',
setup() {
const user = reactive({
name: 'John Doe',
email: 'john@example.com',
age: 25,
isActive: true
})
const updateProfile = () => {
user.name = 'Jane Smith'
user.email = 'jane@example.com'
user.age = 30
}
return {
user,
updateProfile
}
}
}
</script>
2.3 响应式数据的计算属性
计算属性是状态管理中的重要组成部分,它可以帮助我们避免重复计算:
<template>
<div class="todo-list">
<h2>Todo List</h2>
<p>Total todos: {{ totalTodos }}</p>
<p>Completed: {{ completedCount }}</p>
<p>Remaining: {{ remainingCount }}</p>
<ul>
<li v-for="todo in todos" :key="todo.id">
<input
type="checkbox"
:checked="todo.completed"
@change="toggleTodo(todo.id)"
/>
{{ todo.text }}
</li>
</ul>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
name: 'TodoList',
setup() {
const todos = ref([
{ id: 1, text: 'Learn Vue', completed: false },
{ id: 2, text: 'Build an app', completed: true },
{ id: 3, text: 'Deploy to production', completed: false }
])
// 计算属性
const totalTodos = computed(() => todos.value.length)
const completedCount = computed(() =>
todos.value.filter(todo => todo.completed).length
)
const remainingCount = computed(() =>
totalTodos.value - completedCount.value
)
const toggleTodo = (id) => {
const todo = todos.value.find(t => t.id === id)
if (todo) {
todo.completed = !todo.completed
}
}
return {
todos,
totalTodos,
completedCount,
remainingCount,
toggleTodo
}
}
}
</script>
三、组合函数复用机制
3.1 创建可复用的组合函数
组合函数是 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
}
}
<template>
<div class="counter-composable">
<h2>Counter with Composable: {{ count }}</h2>
<p>Double: {{ doubleCount }}</p>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="reset">Reset</button>
</div>
</template>
<script>
import { useCounter } from '@/composables/useCounter'
export default {
name: 'CounterComposable',
setup() {
const { count, increment, decrement, reset, doubleCount } = useCounter(10)
return {
count,
increment,
decrement,
reset,
doubleCount
}
}
}
</script>
3.2 数据获取和状态管理组合函数
// composables/useApi.js
import { ref, reactive } from 'vue'
export function useApi(url) {
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(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
}
}
<template>
<div class="api-data">
<button @click="fetchData" :disabled="loading">Fetch Data</button>
<div v-if="loading">Loading...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else-if="data">
<pre>{{ JSON.stringify(data, null, 2) }}</pre>
</div>
</div>
</template>
<script>
import { useApi } from '@/composables/useApi'
export default {
name: 'ApiData',
setup() {
const { data, loading, error, fetchData } = useApi('/api/data')
return {
data,
loading,
error,
fetchData
}
}
}
</script>
3.3 状态持久化组合函数
// 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
}
<template>
<div class="local-storage">
<input
v-model="user.name"
placeholder="Enter name"
/>
<p>Stored name: {{ user.name }}</p>
</div>
</template>
<script>
import { useLocalStorage } from '@/composables/useLocalStorage'
export default {
name: 'LocalStorageExample',
setup() {
const user = useLocalStorage('user', { name: '' })
return {
user
}
}
}
</script>
四、生命周期管理与副作用处理
4.1 生命周期钩子的使用
在 Composition API 中,我们可以通过 onMounted、onUpdated 等生命周期钩子来处理组件的生命周期事件:
<template>
<div class="lifecycle-example">
<p>Component mounted at: {{ mountTime }}</p>
<p>Click count: {{ clickCount }}</p>
<button @click="handleClick">Click me</button>
</div>
</template>
<script>
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'
export default {
name: 'LifecycleExample',
setup() {
const mountTime = ref(null)
const clickCount = ref(0)
// 组件挂载时
onMounted(() => {
mountTime.value = new Date().toISOString()
console.log('Component mounted')
})
// 组件更新时
onUpdated(() => {
console.log('Component updated')
})
// 组件卸载前
onUnmounted(() => {
console.log('Component will unmount')
})
const handleClick = () => {
clickCount.value++
}
return {
mountTime,
clickCount,
handleClick
}
}
}
</script>
4.2 副作用处理
副作用是组件中会产生外部影响的操作,如 API 调用、定时器等:
<template>
<div class="effect-example">
<p>Current time: {{ currentTime }}</p>
<p>Interval running: {{ isRunning }}</p>
<button @click="toggleTimer">{{ isRunning ? 'Stop' : 'Start' }} Timer</button>
</div>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue'
export default {
name: 'EffectExample',
setup() {
const currentTime = ref(new Date())
const isRunning = ref(false)
let timerId = null
const updateCurrentTime = () => {
currentTime.value = new Date()
}
const toggleTimer = () => {
if (isRunning.value) {
clearInterval(timerId)
isRunning.value = false
} else {
timerId = setInterval(updateCurrentTime, 1000)
isRunning.value = true
}
}
// 组件挂载时启动定时器
onMounted(() => {
timerId = setInterval(updateCurrentTime, 1000)
isRunning.value = true
})
// 组件卸载时清理定时器
onUnmounted(() => {
if (timerId) {
clearInterval(timerId)
}
})
return {
currentTime,
isRunning,
toggleTimer
}
}
}
</script>
4.3 异步操作和错误处理
<template>
<div class="async-example">
<button @click="fetchUserData" :disabled="loading">Fetch User</button>
<div v-if="loading">Loading...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else-if="user">
<h3>{{ user.name }}</h3>
<p>Email: {{ user.email }}</p>
<p>Website: {{ user.website }}</p>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'AsyncExample',
setup() {
const user = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchUserData = async () => {
try {
loading.value = true
error.value = null
// 模拟 API 调用
const response = await fetch('/api/user')
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
user.value = await response.json()
} catch (err) {
error.value = err.message
console.error('Error fetching user data:', err)
} finally {
loading.value = false
}
}
return {
user,
loading,
error,
fetchUserData
}
}
}
</script>
五、复杂组件状态管理方案
5.1 多级嵌套组件的状态管理
在复杂的组件树中,我们可以通过组合函数来管理跨层级的状态:
// composables/useTheme.js
import { ref, computed } from 'vue'
export function useTheme() {
const theme = ref('light')
const isDarkMode = computed(() => theme.value === 'dark')
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
return {
theme,
isDarkMode,
toggleTheme
}
}
// composables/useUser.js
import { ref, reactive } from 'vue'
export function useUser() {
const user = ref(null)
const isAuthenticated = ref(false)
const login = (userData) => {
user.value = userData
isAuthenticated.value = true
}
const logout = () => {
user.value = null
isAuthenticated.value = false
}
return {
user,
isAuthenticated,
login,
logout
}
}
<template>
<div class="app" :class="{ 'dark-theme': isDarkMode }">
<header class="app-header">
<h1>My Vue App</h1>
<button @click="toggleTheme">{{ isDarkMode ? 'Light Mode' : 'Dark Mode' }}</button>
<div v-if="isAuthenticated">
<span>Welcome, {{ user?.name }}!</span>
<button @click="logout">Logout</button>
</div>
</header>
<main class="app-main">
<router-view />
</main>
</div>
</template>
<script>
import { useTheme } from '@/composables/useTheme'
import { useUser } from '@/composables/useUser'
export default {
name: 'App',
setup() {
const { theme, isDarkMode, toggleTheme } = useTheme()
const { user, isAuthenticated, logout } = useUser()
return {
theme,
isDarkMode,
toggleTheme,
user,
isAuthenticated,
logout
}
}
}
</script>
5.2 表单状态管理
复杂的表单处理需要良好的状态管理策略:
<template>
<form class="user-form" @submit.prevent="handleSubmit">
<div class="form-group">
<label for="name">Name:</label>
<input
id="name"
v-model="formData.name"
type="text"
:class="{ 'error': errors.name }"
/>
<span v-if="errors.name" class="error-message">{{ errors.name }}</span>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input
id="email"
v-model="formData.email"
type="email"
:class="{ 'error': errors.email }"
/>
<span v-if="errors.email" class="error-message">{{ errors.email }}</span>
</div>
<div class="form-group">
<label for="age">Age:</label>
<input
id="age"
v-model.number="formData.age"
type="number"
:class="{ 'error': errors.age }"
/>
<span v-if="errors.age" class="error-message">{{ errors.age }}</span>
</div>
<button type="submit" :disabled="isSubmitting">Submit</button>
<button type="button" @click="resetForm">Reset</button>
</form>
</template>
<script>
import { reactive, computed } from 'vue'
export default {
name: 'UserForm',
setup() {
const formData = reactive({
name: '',
email: '',
age: null
})
const errors = reactive({})
const isSubmitting = ref(false)
// 验证规则
const validateField = (field, value) => {
switch (field) {
case 'name':
return value && value.length >= 2 ? null : 'Name must be at least 2 characters'
case 'email':
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(value) ? null : 'Please enter a valid email'
case 'age':
return value && value >= 0 && value <= 150 ? null : 'Please enter a valid age'
default:
return null
}
}
const validateForm = () => {
const newErrors = {}
Object.keys(formData).forEach(key => {
const error = validateField(key, formData[key])
if (error) {
newErrors[key] = error
}
})
Object.assign(errors, newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = async () => {
if (!validateForm()) {
return
}
try {
isSubmitting.value = true
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 1000))
console.log('Form submitted:', formData)
// 这里可以添加实际的 API 调用
} catch (error) {
console.error('Submission error:', error)
} finally {
isSubmitting.value = false
}
}
const resetForm = () => {
Object.keys(formData).forEach(key => {
formData[key] = ''
})
Object.keys(errors).forEach(key => {
delete errors[key]
})
}
return {
formData,
errors,
isSubmitting,
handleSubmit,
resetForm
}
}
}
</script>
<style scoped>
.user-form {
max-width: 400px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 4px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
input.error {
border-color: #ff4757;
}
.error-message {
color: #ff4757;
font-size: 12px;
margin-top: 5px;
}
button {
padding: 10px 15px;
margin-right: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button[type="submit"] {
background-color: #3742fa;
color: white;
}
button[type="submit"]:disabled {
background-color: #ccc;
cursor: not-allowed;
}
button[type="button"] {
background-color: #6c757d;
color: white;
}
</style>
5.3 状态同步和通信
在组件间传递复杂状态时,我们需要考虑状态同步的机制:
// composables/useGlobalState.js
import { reactive, watch } from 'vue'
export function useGlobalState() {
const state = reactive({
theme: 'light',
language: 'en',
notifications: [],
currentUser: null
})
// 状态变更监听器
watch(state, (newState) => {
// 持久化到 localStorage
localStorage.setItem('app-state', JSON.stringify(newState))
// 发布状态变更事件
window.dispatchEvent(new CustomEvent('app-state-change', {
detail: newState
}))
}, { deep: true })
// 从 localStorage 初始化状态
const initFromStorage = () => {
const savedState = localStorage.getItem('app-state')
if (savedState) {
Object.assign(state, JSON.parse(savedState))
}
}
// 添加通知
const addNotification = (notification) => {
state.notifications.push({
id: Date.now(),
...notification,
timestamp: new Date()
})
// 自动清理旧通知
if (state.notifications.length > 10) {
state.notifications.shift()
}
}
// 移除通知
const removeNotification = (id) => {
const index = state.notifications.findIndex(n => n.id === id)
if (index !== -1) {
state.notifications.splice(index, 1)
}
}
initFromStorage()
return {
state,
addNotification,
removeNotification
}
}
六、性能优化与最佳实践
6.1 响应式数据的优化
合理使用响应式系统可以显著提升应用性能:
<template>
<div class="performance-example">
<p>Large list rendering: {{ items.length }} items</p>
<ul>
<li v-for="item in visibleItems" :key="item.id">
{{ item.name }}
</li>
</ul>
<button @click="loadMore">Load More</button>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
name: 'PerformanceExample',
setup() {
const items = ref([])
const page = ref(1)
// 使用计算属性避免重复计算
const visibleItems = computed(() => {
return items.value.slice(0, page.value * 20)
})
const loadMore = () => {
page.value++
// 模拟数据加载
setTimeout(() => {
const newItems = Array.from({ length: 20 }, (_, i) => ({
id: Date.now() + i,
name: `Item ${items.value.length + i + 1}`
}))
items.value.push(...newItems)
}, 100)
}
return {
items,
visibleItems,
loadMore
}
}
}
</script>
6.2 避免不必要的重新渲染
<template>
<div class="memo-example">
<p>Expensive calculation: {{ expensiveValue }}</p>
<button @click="updateData">Update Data</button>
<button @click="forceUpdate">Force Update</button>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
name: 'MemoExample',
setup() {
const data = ref(0)
// 使用计算属性缓存昂贵的计算
const expensiveValue = computed(() => {
console.log('Computing expensive value...')
return Math.pow(data.value, 2) + Math.sqrt(data.value)
})
const updateData = () => {
data.value++
}
const forceUpdate = () => {
// 强制更新的方案
data.value = data.value
}
return {
expensiveValue,
updateData,
forceUpdate
}
}
}
</script>
6.3 组件间通信的最佳实践
<template>
<div class="communication-example">
<parent-component />
</div>
</template>
<script>
import ParentComponent from './ParentComponent.vue'
export default {
name: 'CommunicationExample',
components: {
ParentComponent
}
}
</script>
<!-- ParentComponent.vue -->
<template>
<div class="parent">
<h2>Parent Component</h2>
<p>Shared data: {{ sharedData }}</p>
<child-component :shared-data="sharedData" @update-shared-data="handleUpdate" />
</div>
</template>
<script>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
export default {
name: 'ParentComponent',
components: {
ChildComponent
},
setup() {
const sharedData = ref('Initial value')
const handleUpdate = (newValue) => {
sharedData.value = newValue
}
return {
sharedData,
handleUpdate
}
}
}
</script>
<!-- ChildComponent.vue -->
<template>
<div class="child">
<h3>Child Component</h3>
<p>Received data: {{ sharedData }}</p
评论 (0)