引言
Vue.js 3.0 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。相比于传统的 Options API,Composition API 提供了更灵活、更强大的组件开发方式,特别是在处理复杂逻辑和组件复用方面表现卓越。
在现代前端开发中,响应式编程已经成为一种重要的开发范式。Vue3 的响应式系统基于 ES6 的 Proxy 和 Reflect API,为开发者提供了更加直观和高效的响应式数据管理能力。同时,通过组合函数(Composable Functions)的概念,我们能够将可复用的逻辑从组件中抽离出来,实现真正的代码复用。
本文将深入探讨 Vue3 Composition API 的核心特性,通过丰富的实战示例,帮助开发者掌握响应式编程和组件复用的最佳实践。
Vue3 响应式系统详解
响应式基础概念
Vue3 的响应式系统是整个 Composition API 的基石。与 Vue2 使用 Object.defineProperty 不同,Vue3 采用 Proxy 来实现响应式,这带来了更好的性能和更丰富的功能。
import { reactive, ref, computed } from 'vue'
// 使用 ref 创建响应式数据
const count = ref(0)
console.log(count.value) // 0
// 使用 reactive 创建响应式对象
const state = reactive({
name: 'Vue',
version: '3.0'
})
// 修改值
count.value++
state.version = '3.1'
ref vs reactive 的区别
import { ref, reactive } from 'vue'
// ref - 适用于基本数据类型和对象的引用
const count = ref(0)
const message = ref('Hello')
// reactive - 适用于复杂对象
const user = reactive({
name: 'John',
age: 30,
address: {
city: 'Beijing',
country: 'China'
}
})
// 访问和修改
console.log(count.value) // 0
count.value = 10
user.name = 'Jane' // 直接修改,无需 .value
user.address.city = 'Shanghai' // 嵌套对象自动响应式
响应式数据的深度监听
import { reactive } from 'vue'
const data = reactive({
user: {
profile: {
name: 'Alice',
settings: {
theme: 'dark'
}
}
}
})
// 深度响应式对象可以被直接修改
data.user.profile.name = 'Bob'
data.user.profile.settings.theme = 'light'
// 响应式系统会自动追踪所有层级的变化
Composition API 核心特性
setup 函数详解
setup 函数是 Composition API 的入口点,它在组件实例创建之前执行,负责初始化响应式数据和逻辑。
<template>
<div>
<p>计数器: {{ count }}</p>
<button @click="increment">增加</button>
<p>计算结果: {{ doubledCount }}</p>
<p>用户信息: {{ userInfo.name }} - {{ userInfo.age }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
// 响应式数据
const count = ref(0)
const userInfo = ref({
name: 'Vue',
age: 3
})
// 计算属性
const doubledCount = computed(() => count.value * 2)
// 方法
const increment = () => {
count.value++
}
// 生命周期钩子
import { onMounted, onUnmounted } from 'vue'
onMounted(() => {
console.log('组件已挂载')
})
onUnmounted(() => {
console.log('组件即将卸载')
})
</script>
响应式数据的高级用法
import { ref, reactive, watch, watchEffect } from 'vue'
// 创建响应式数组
const items = ref([])
items.value.push(1)
items.value.length = 0 // 触发响应式更新
// 使用 reactive 管理复杂对象
const state = reactive({
user: {
profile: {
name: '',
email: ''
}
},
loading: false,
error: null
})
// 监听响应式数据变化
watch(count, (newVal, oldVal) => {
console.log(`计数从 ${oldVal} 变为 ${newVal}`)
})
// 监听多个数据源
watch([count, userInfo], ([newCount, newUser], [oldCount, oldUser]) => {
console.log('数据发生变化')
})
// watchEffect - 自动追踪依赖
watchEffect(() => {
console.log(`当前计数: ${count.value}`)
console.log(`用户姓名: ${userInfo.value.name}`)
})
组合函数(Composables)实战
创建可复用的组合函数
组合函数是 Vue3 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 doubled = computed(() => count.value * 2)
return {
count,
increment,
decrement,
reset,
doubled
}
}
// composables/useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
const value = ref(defaultValue)
// 从 localStorage 初始化
try {
const stored = localStorage.getItem(key)
if (stored) {
value.value = JSON.parse(stored)
}
} catch (error) {
console.error('localStorage 读取失败:', error)
}
// 监听变化并同步到 localStorage
watch(value, (newValue) => {
try {
localStorage.setItem(key, JSON.stringify(newValue))
} catch (error) {
console.error('localStorage 写入失败:', error)
}
}, { deep: true })
return value
}
// composables/useApi.js
import { ref, computed } from 'vue'
export function useApi(url, options = {}) {
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, options)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
data.value = await response.json()
} catch (err) {
error.value = err.message
console.error('API 请求失败:', err)
} finally {
loading.value = false
}
}
const refresh = () => fetchData()
// 自动加载数据
fetchData()
return {
data,
loading,
error,
refresh
}
}
组合函数在组件中的使用
<template>
<div>
<!-- 计数器示例 -->
<h2>计数器</h2>
<p>计数: {{ counter.count }}</p>
<p>双倍计数: {{ counter.doubled }}</p>
<button @click="counter.increment">增加</button>
<button @click="counter.decrement">减少</button>
<button @click="counter.reset">重置</button>
<!-- 本地存储示例 -->
<h2>用户设置</h2>
<input v-model="userSettings.theme" placeholder="主题">
<input v-model="userSettings.language" placeholder="语言">
<!-- API 数据示例 -->
<h2>用户列表</h2>
<div v-if="users.loading">加载中...</div>
<div v-else-if="users.error">{{ users.error }}</div>
<ul v-else>
<li v-for="user in users.data" :key="user.id">
{{ user.name }} - {{ user.email }}
</li>
</ul>
<button @click="users.refresh">刷新</button>
</div>
</template>
<script setup>
import { useCounter } from './composables/useCounter'
import { useLocalStorage } from './composables/useLocalStorage'
import { useApi } from './composables/useApi'
// 使用组合函数
const counter = useCounter(0)
const userSettings = useLocalStorage('userSettings', {
theme: 'light',
language: 'zh-CN'
})
const users = useApi('/api/users')
</script>
高级响应式编程技巧
响应式数据的条件监听
import { ref, watch } from 'vue'
const condition = ref(false)
const data = ref({})
// 只在条件为真时才监听
watch(
() => condition.value ? data.value : null,
(newVal) => {
if (newVal) {
console.log('数据发生变化:', newVal)
}
}
)
// 使用 watchEffect 的条件逻辑
import { watchEffect } from 'vue'
watchEffect(() => {
if (condition.value) {
console.log('监听数据:', data.value)
}
})
响应式数据的深度监听优化
import { ref, watch } from 'vue'
const state = ref({
user: {
profile: {
name: 'John',
settings: {
theme: 'dark'
}
}
}
})
// 避免不必要的深度监听
watch(
() => state.value.user.profile.name,
(newName) => {
console.log('用户名改变:', newName)
}
)
// 使用 watch 的 deep 选项时要谨慎
watch(
state,
(newState) => {
console.log('整个状态对象变化')
},
{ deep: true, flush: 'post' } // post 表示在 DOM 更新后执行
)
响应式数据的异步处理
import { ref, computed, watch } from 'vue'
const searchQuery = ref('')
const searchResults = ref([])
const loading = ref(false)
// 防抖搜索
const debouncedSearch = debounce(async (query) => {
if (!query.trim()) {
searchResults.value = []
return
}
loading.value = true
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`)
searchResults.value = await response.json()
} catch (error) {
console.error('搜索失败:', error)
} finally {
loading.value = false
}
}, 300)
watch(searchQuery, debouncedSearch)
// 计算属性的异步处理
const formattedResults = computed(() => {
return searchResults.value.map(item => ({
...item,
formattedDate: new Date(item.createdAt).toLocaleDateString()
}))
})
// 防抖函数实现
function debounce(func, wait) {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
组件生命周期管理
在 Composition API 中使用生命周期钩子
<template>
<div>
<p>组件状态: {{ status }}</p>
<button @click="triggerEvent">触发事件</button>
</div>
</template>
<script setup>
import { ref, onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUpdate, onBeforeUnmount } from 'vue'
const status = ref('初始化')
const eventCount = ref(0)
// 生命周期钩子
onBeforeMount(() => {
console.log('组件即将挂载')
status.value = '准备挂载'
})
onMounted(() => {
console.log('组件已挂载')
status.value = '已挂载'
// 设置定时器
const timer = setInterval(() => {
console.log('定时器执行')
}, 1000)
// 在卸载时清理定时器
onUnmounted(() => {
clearInterval(timer)
console.log('定时器已清理')
})
})
onBeforeUpdate(() => {
console.log('组件即将更新')
})
onUpdated(() => {
console.log('组件已更新')
})
onBeforeUnmount(() => {
console.log('组件即将卸载')
})
const triggerEvent = () => {
eventCount.value++
}
// 清理函数示例
const cleanup = onMounted(() => {
const observer = new MutationObserver((mutations) => {
console.log('DOM 变化:', mutations)
})
// 开始观察
observer.observe(document.body, { childList: true, subtree: true })
// 返回清理函数
return () => {
observer.disconnect()
console.log('观察器已断开')
}
})
</script>
异步生命周期处理
import { ref, onMounted, onUnmounted } from 'vue'
export function useAsyncLifecycle() {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
let cleanupFunctions = []
const initialize = async () => {
try {
loading.value = true
error.value = null
// 模拟异步数据加载
await new Promise(resolve => setTimeout(resolve, 1000))
data.value = '异步数据加载完成'
// 添加清理函数
const cleanup = () => {
console.log('清理异步资源')
}
cleanupFunctions.push(cleanup)
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
onMounted(() => {
initialize()
})
onUnmounted(() => {
// 执行所有清理函数
cleanupFunctions.forEach(fn => fn())
})
return {
data,
loading,
error
}
}
组件复用的最佳实践
复杂组件的逻辑抽离
<template>
<div class="form-container">
<form @submit.prevent="handleSubmit">
<div class="form-group">
<label>用户名</label>
<input v-model="formData.username" type="text" required />
</div>
<div class="form-group">
<label>邮箱</label>
<input v-model="formData.email" type="email" required />
</div>
<div class="form-group">
<label>密码</label>
<input v-model="formData.password" type="password" required />
</div>
<button type="submit" :disabled="isSubmitting">提交</button>
</form>
<div v-if="submitError" class="error">{{ submitError }}</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { useFormValidation, useApiSubmit } from './composables/useForm'
// 表单数据
const formData = reactive({
username: '',
email: '',
password: ''
})
// 表单验证
const { errors, validate } = useFormValidation(formData, {
username: { required: true, minLength: 3 },
email: { required: true, email: true },
password: { required: true, minLength: 6 }
})
// API 提交处理
const { submit, isSubmitting, submitError } = useApiSubmit('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
// 处理提交
const handleSubmit = async () => {
if (!validate()) {
return
}
try {
await submit(formData)
// 提交成功后的处理
console.log('注册成功')
} catch (error) {
console.error('提交失败:', error)
}
}
</script>
<style scoped>
.form-container {
max-width: 400px;
margin: 20px auto;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.error {
color: red;
margin-top: 10px;
}
</style>
组合函数的依赖注入
// composables/useTheme.js
import { ref, provide, inject } from 'vue'
export function useTheme() {
const theme = ref('light')
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
provide('theme', { theme, toggleTheme })
return { theme, toggleTheme }
}
export function useThemeContext() {
const context = inject('theme')
if (!context) {
throw new Error('useThemeContext 必须在 useTheme 的上下文中使用')
}
return context
}
// composables/useAuth.js
import { ref, provide, inject } from 'vue'
export function useAuth() {
const user = ref(null)
const token = ref('')
const isAuthenticated = computed(() => !!user.value)
const login = async (credentials) => {
// 模拟登录
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
})
const data = await response.json()
user.value = data.user
token.value = data.token
localStorage.setItem('token', data.token)
} catch (error) {
throw new Error('登录失败')
}
}
const logout = () => {
user.value = null
token.value = ''
localStorage.removeItem('token')
}
provide('auth', { user, token, isAuthenticated, login, logout })
return { user, token, isAuthenticated, login, logout }
}
export function useAuthContext() {
const context = inject('auth')
if (!context) {
throw new Error('useAuthContext 必须在 useAuth 的上下文中使用')
}
return context
}
性能优化策略
响应式数据的合理使用
import { ref, computed, watch } from 'vue'
// 避免不必要的响应式包装
const plainData = {
name: 'Vue',
version: '3.0'
}
// 正确的做法 - 只对需要响应式的属性使用 ref/reactive
const reactiveData = reactive({
items: [],
loading: false
})
const computedValue = computed(() => {
// 复杂计算,避免在模板中直接写复杂逻辑
return reactiveData.items.reduce((sum, item) => sum + item.value, 0)
})
// 监听优化 - 只监听必要的数据
watch(
() => reactiveData.loading,
(newLoading) => {
if (newLoading) {
console.log('开始加载')
}
}
)
// 避免在 watch 中进行复杂操作
const optimizedWatch = watch(
computedValue,
(newValue) => {
// 简单的副作用处理
document.title = `总和: ${newValue}`
},
{ flush: 'post' } // 在 DOM 更新后执行
)
计算属性的最佳实践
import { ref, computed } from 'vue'
const items = ref([])
const filterText = ref('')
const categoryFilter = ref('all')
// 复杂的计算属性
const filteredItems = computed(() => {
return items.value.filter(item => {
const matchesText = item.name.toLowerCase().includes(filterText.value.toLowerCase())
const matchesCategory = categoryFilter.value === 'all' || item.category === categoryFilter.value
return matchesText && matchesCategory
})
})
// 高性能的计算属性缓存
const expensiveCalculation = computed(() => {
// 这个计算可能很耗时,但会被缓存
return items.value.reduce((acc, item) => {
// 复杂的计算逻辑
return acc + item.price * item.quantity
}, 0)
})
// 使用 getter/setter 的计算属性
const totalWithTax = computed({
get: () => expensiveCalculation.value * 1.1,
set: (value) => {
// 当设置值时的处理逻辑
console.log('总金额变化:', value)
}
})
实际项目应用案例
用户管理组件实战
<template>
<div class="user-management">
<!-- 搜索和过滤 -->
<div class="search-filters">
<input v-model="searchQuery" placeholder="搜索用户..." />
<select v-model="selectedRole">
<option value="">所有角色</option>
<option value="admin">管理员</option>
<option value="user">普通用户</option>
</select>
</div>
<!-- 用户列表 -->
<div class="user-list">
<div v-for="user in filteredUsers" :key="user.id" class="user-item">
<img :src="user.avatar" :alt="user.name" />
<div class="user-info">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<span class="role">{{ user.role }}</span>
</div>
<div class="actions">
<button @click="editUser(user)">编辑</button>
<button @click="deleteUser(user.id)" class="delete">删除</button>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<button @click="currentPage--" :disabled="currentPage === 1">上一页</button>
<span>第 {{ currentPage }} 页</span>
<button @click="currentPage++" :disabled="currentPage >= totalPages">下一页</button>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="loading">加载中...</div>
<div v-else-if="error" class="error">{{ error }}</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { useApi } from './composables/useApi'
// 响应式数据
const searchQuery = ref('')
const selectedRole = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
// API 数据获取
const { data: users, loading, error, refresh } = useApi('/api/users')
// 计算属性 - 过滤和分页
const filteredUsers = computed(() => {
if (!users.value) return []
let filtered = users.value.filter(user => {
const matchesSearch = user.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
user.email.toLowerCase().includes(searchQuery.value.toLowerCase())
const matchesRole = selectedRole.value === '' || user.role === selectedRole.value
return matchesSearch && matchesRole
})
// 分页处理
const startIndex = (currentPage.value - 1) * pageSize.value
return filtered.slice(startIndex, startIndex + pageSize.value)
})
const totalPages = computed(() => {
if (!users.value) return 0
return Math.ceil(users.value.length / pageSize.value)
})
// 监听分页变化
watch(currentPage, () => {
// 可以添加滚动到顶部的逻辑
window.scrollTo({ top: 0, behavior: 'smooth' })
})
// 方法
const editUser = (user) => {
console.log('编辑用户:', user)
}
const deleteUser = async (userId) => {
if (confirm('确定要删除这个用户吗?')) {
try {
await fetch(`/api/users/${userId}`, { method: 'DELETE' })
refresh() // 刷新数据
} catch (error) {
console.error('删除失败:', error)
}
}
}
// 初始化数据
refresh()
</script>
<style scoped>
.user-management {
padding: 20px;
}
.search-filters {
display: flex;
gap: 10px;
margin-bottom: 20px;
align-items: center;
}
.search-filters input,
.search-filters select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.user-list {
display: flex;
flex-direction: column;
gap: 15px;
}
.user-item {
display: flex;
align-items: center;
gap: 15px;
padding: 15px;
border: 1px solid #eee;
border-radius: 8px;
}
.user-item img {
width: 50px;
height: 50px;
border-radius: 50%;
}
.user-info h3 {
margin: 0 0 5px 0;
}
.role {
background-color: #f0f0f0;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
}
.actions {
margin-left: auto;
display: flex;
gap: 10px;
}
.delete {
background-color: #ff4757;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-top: 20px;
}
.pagination button {
padding: 8px 16px;
border: 1px solid #ddd;
background-color: white;
cursor: pointer;
}
.pagination button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.loading,
.error {
text-align: center;
padding: 20px;
}
</style>
总结与展望
Vue3 Composition API 的引入为前端开发带来了革命性的变化。通过响应式编程,我们能够更直观地管理组件状态;通过组合函数,我们可以实现真正意义上的代码复用;通过灵活的生命周期管理,我们能够更好地控制组件的行为。
在实际项目中,合理的使用 Composition API 可以显著提升代码的可维护性和可读性。我们建议开发者:
- 循序渐进地采用:从简单的场景开始,逐步掌握更复杂的用法
- 合理设计组合函数:确保组合函数的单一职责和高复用性
- 关注性能优化:避免不必要的响应式包装和监听
- 善用计算属性:充分利用缓存机制提升性能
随着 Vue 生态系统的不断完善,Composition API 将在更多场景中发挥作用。未来的开发趋势将更加注重逻辑的可复用性和组件的灵活性,而 Composition API 正是实现这一目标的重要工具。
通过本文的详细介绍和实战示例,相信读者已经对 Vue3 Composition API 有了深入的理解,并能够在实际项目中灵活运用这些技术,提升前端开发效率和代码质量

评论 (0)