引言
Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。相比传统的 Options API,Composition API 提供了更灵活、更强大的组件开发方式,特别是在处理复杂逻辑和跨组件通信方面表现卓越。本文将深入探讨 Vue 3 Composition API 的核心特性,从基础概念到高级应用,帮助开发者全面掌握这一现代前端开发技术。
Vue 3 Composition API 核心概念
什么是 Composition API
Composition API 是 Vue 3 中引入的一种新的组件开发方式,它允许我们通过组合函数来组织和复用逻辑代码。与传统的 Options API 不同,Composition API 更加灵活,能够更好地处理复杂的业务逻辑,特别是在大型应用中。
核心响应式函数
Composition API 的基础是几个核心的响应式函数:
import { ref, reactive, computed, watch } from 'vue'
// 创建响应式数据
const count = ref(0)
const user = reactive({ name: 'John', age: 25 })
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 侦听器
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
setup 函数
setup 是 Composition API 的入口函数,所有逻辑都写在这里:
import { ref, reactive } from 'vue'
export default {
setup() {
const count = ref(0)
const user = reactive({ name: 'John', age: 25 })
return {
count,
user
}
}
}
组件间通信模式
Props 传递数据
在 Composition API 中,props 的使用方式与 Options API 略有不同:
// 子组件
import { computed } from 'vue'
export default {
props: {
title: String,
user: Object
},
setup(props) {
// props 是响应式的
const displayName = computed(() => {
return props.user?.name || 'Anonymous'
})
return {
displayName
}
}
}
emit 事件传递
// 子组件
import { ref } from 'vue'
export default {
emits: ['update:count', 'submit'],
setup(props, { emit }) {
const count = ref(0)
const handleIncrement = () => {
count.value++
emit('update:count', count.value)
}
const handleSubmit = (data) => {
emit('submit', data)
}
return {
count,
handleIncrement,
handleSubmit
}
}
}
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)
return {
theme,
user
}
}
}
// 子组件
import { inject } from 'vue'
export default {
setup() {
const theme = inject('theme')
const user = inject('user')
return {
theme,
user
}
}
}
全局状态管理
对于复杂应用,可以使用 provide/inject 实现简单的全局状态管理:
// store.js
import { reactive, readonly } from 'vue'
export const store = reactive({
state: {
user: null,
theme: 'light',
notifications: []
},
setUser(user) {
this.state.user = user
},
setTheme(theme) {
this.state.theme = theme
},
addNotification(notification) {
this.state.notifications.push(notification)
}
})
export const useStore = () => {
return readonly(store)
}
// main.js
import { createApp } from 'vue'
import { store } from './store'
const app = createApp(App)
app.provide('store', store)
// 组件中使用
import { inject } from 'vue'
export default {
setup() {
const store = inject('store')
const login = (userData) => {
store.setUser(userData)
}
return {
login,
user: computed(() => store.state.user)
}
}
}
响应式数据管理
ref vs reactive
理解 ref 和 reactive 的区别至关重要:
import { ref, reactive } from 'vue'
// ref 用于基本类型和对象的包装
const count = ref(0) // 等价于 { value: 0 }
const name = ref('John') // 等价于 { value: 'John' }
// reactive 用于创建响应式对象
const user = reactive({
name: 'John',
age: 25,
address: {
city: 'Beijing',
country: 'China'
}
})
// 使用时的区别
console.log(count.value) // 0
console.log(user.name) // John
深层响应式处理
import { reactive, watch } from 'vue'
const state = reactive({
user: {
profile: {
name: 'John',
settings: {
theme: 'dark',
notifications: true
}
}
}
})
// 监听深层变化
watch(
() => state.user.profile.settings,
(newSettings, oldSettings) => {
console.log('Settings changed:', newSettings)
},
{ deep: true }
)
// 或者使用 watchEffect
watchEffect(() => {
console.log(state.user.profile.name)
})
响应式数据的性能优化
import { ref, computed, watchEffect } from 'vue'
export default {
setup() {
const items = ref([])
const searchTerm = ref('')
// 使用 computed 缓存计算结果
const filteredItems = computed(() => {
return items.value.filter(item =>
item.name.toLowerCase().includes(searchTerm.value.toLowerCase())
)
})
// 使用 watchEffect 优化副作用
watchEffect(() => {
// 只在依赖变化时执行
console.log(`Found ${filteredItems.value.length} items`)
})
return {
items,
searchTerm,
filteredItems
}
}
}
计算属性与侦听器详解
计算属性的高级用法
import { computed, ref } from 'vue'
export default {
setup() {
const firstName = ref('John')
const lastName = ref('Doe')
const age = ref(25)
// 基础计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 带有 getter 和 setter 的计算属性
const displayName = computed({
get: () => {
return `${firstName.value} ${lastName.value}`
},
set: (value) => {
const names = value.split(' ')
firstName.value = names[0]
lastName.value = names[1]
}
})
// 复杂计算属性
const userStats = computed(() => {
return {
name: fullName.value,
isAdult: age.value >= 18,
yearsUntilRetirement: Math.max(0, 65 - age.value)
}
})
return {
firstName,
lastName,
age,
fullName,
displayName,
userStats
}
}
}
侦听器的灵活应用
import { ref, watch, watchEffect } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('John')
const items = ref([])
// 基础侦听器
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`)
})
// 侦听多个源
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log(`Count: ${newCount}, Name: ${newName}`)
})
// 深度侦听
watch(items, (newItems) => {
console.log('Items array changed:', newItems)
}, { deep: true })
// 立即执行的侦听器
watch(count, (newValue) => {
console.log('Immediate watcher:', newValue)
}, { immediate: true })
// 侦听器清理函数
const stopWatcher = watch(count, (newValue) => {
// 模拟异步操作
const timer = setTimeout(() => {
console.log('Processed:', newValue)
}, 1000)
return () => {
clearTimeout(timer)
}
})
// watchEffect - 自动追踪依赖
watchEffect(() => {
console.log(`Name: ${name.value}, Count: ${count.value}`)
// 会自动追踪 name 和 count 的变化
})
return {
count,
name,
items,
stopWatcher
}
}
}
高级侦听器模式
import { ref, watchEffect, computed } from 'vue'
export default {
setup() {
const searchQuery = ref('')
const results = ref([])
const loading = ref(false)
// 防抖搜索
const debouncedSearch = (query) => {
if (!query) {
results.value = []
return
}
loading.value = true
// 模拟 API 调用
setTimeout(() => {
results.value = [
{ id: 1, name: `${query} result 1` },
{ id: 2, name: `${query} result 2` }
]
loading.value = false
}, 300)
}
// 使用 watchEffect 实现防抖
watchEffect(() => {
if (searchQuery.value) {
debouncedSearch(searchQuery.value)
} else {
results.value = []
loading.value = false
}
})
// 节流操作
const throttledSave = throttle((data) => {
console.log('Saving data:', data)
// 实际的保存逻辑
}, 1000)
function throttle(func, limit) {
let inThrottle
return function() {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
return {
searchQuery,
results,
loading,
throttledSave
}
}
}
性能优化策略
组件渲染优化
import { ref, computed, shallowRef, markRaw } from 'vue'
export default {
setup() {
const count = ref(0)
const expensiveData = ref(null)
// 使用 shallowRef 浅层响应式,避免深层递归追踪
const shallowData = shallowRef({
name: 'John',
items: [1, 2, 3]
})
// 对于不需要响应式的对象,使用 markRaw
const nonReactiveObject = markRaw({
id: 1,
name: 'Non-reactive'
})
// 计算属性优化
const expensiveComputation = computed(() => {
// 复杂计算,使用缓存避免重复执行
return Array.from({ length: 1000 }, (_, i) => i * i)
})
// 使用 memoization 缓存函数结果
const memoizedFunction = (input) => {
if (!memoizedFunction.cache) {
memoizedFunction.cache = new Map()
}
if (memoizedFunction.cache.has(input)) {
return memoizedFunction.cache.get(input)
}
const result = expensiveComputation.value[input] || 0
memoizedFunction.cache.set(input, result)
return result
}
return {
count,
expensiveData,
shallowData,
nonReactiveObject,
expensiveComputation,
memoizedFunction
}
}
}
渲染性能优化
<template>
<div>
<!-- 使用 v-memo 提高性能 -->
<div v-for="item in items" :key="item.id">
<div v-memo="[item.name, item.value]">
{{ item.name }}: {{ item.value }}
</div>
</div>
<!-- 条件渲染优化 -->
<div v-if="showDetails">
<component :is="dynamicComponent" />
</div>
<!-- 列表渲染优化 -->
<div v-for="(item, index) in optimizedList" :key="index">
{{ item }}
</div>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
setup() {
const items = ref([])
const showDetails = ref(false)
const dynamicComponent = ref(null)
// 使用计算属性优化列表
const optimizedList = computed(() => {
return items.value.slice(0, 100) // 只渲染前100项
})
// 避免不必要的重新渲染
const expensiveComputed = computed(() => {
// 只在依赖变化时计算
return items.value.reduce((acc, item) => acc + item.value, 0)
})
return {
items,
showDetails,
dynamicComponent,
optimizedList,
expensiveComputed
}
}
}
</script>
异步数据处理优化
import { ref, onMounted, onUnmounted } from 'vue'
export default {
setup() {
const data = ref([])
const loading = ref(false)
const error = ref(null)
// 使用 async/await 处理异步操作
const fetchData = async () => {
try {
loading.value = true
error.value = null
// 模拟 API 调用
const response = await fetch('/api/data')
const result = await response.json()
data.value = result
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 组件卸载时取消请求
let abortController = null
const fetchWithAbort = async () => {
if (abortController) {
abortController.abort()
}
abortController = new AbortController()
try {
loading.value = true
error.value = null
const response = await fetch('/api/data', {
signal: abortController.signal
})
if (!response.ok) {
throw new Error('Network response was not ok')
}
const result = await response.json()
data.value = result
} catch (err) {
if (err.name !== 'AbortError') {
error.value = err.message
}
} finally {
loading.value = false
}
}
// 组件挂载时获取数据
onMounted(() => {
fetchData()
})
// 组件卸载时清理
onUnmounted(() => {
if (abortController) {
abortController.abort()
}
})
return {
data,
loading,
error,
fetchData,
fetchWithAbort
}
}
}
实际应用案例
复杂表单管理
<template>
<form @submit.prevent="handleSubmit">
<div class="form-group">
<label>姓名</label>
<input v-model="formData.name" type="text" />
</div>
<div class="form-group">
<label>邮箱</label>
<input v-model="formData.email" type="email" />
</div>
<div class="form-group">
<label>年龄</label>
<input v-model.number="formData.age" type="number" />
</div>
<button type="submit" :disabled="isSubmitting">提交</button>
<button type="button" @click="resetForm">重置</button>
</form>
<div v-if="validationErrors.length > 0">
<ul>
<li v-for="error in validationErrors" :key="error">{{ error }}</li>
</ul>
</div>
</template>
<script>
import { ref, reactive, computed, watch } from 'vue'
export default {
setup() {
const formData = reactive({
name: '',
email: '',
age: null
})
const isSubmitting = ref(false)
const validationErrors = ref([])
// 表单验证规则
const validateForm = computed(() => {
return {
name: !formData.name || formData.name.length < 2,
email: !formData.email || !isValidEmail(formData.email),
age: formData.age === null || formData.age < 0 || formData.age > 150
}
})
// 验证邮箱格式
const isValidEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email)
}
// 实时验证
watch(formData, () => {
validationErrors.value = []
if (validateForm.value.name) {
validationErrors.value.push('姓名至少需要2个字符')
}
if (validateForm.value.email) {
validationErrors.value.push('请输入有效的邮箱地址')
}
if (validateForm.value.age) {
validationErrors.value.push('年龄必须在0-150之间')
}
}, { deep: true })
// 表单提交
const handleSubmit = async () => {
if (validationErrors.value.length > 0) {
return
}
isSubmitting.value = true
try {
await submitForm(formData)
console.log('表单提交成功')
} catch (error) {
console.error('表单提交失败:', error)
} finally {
isSubmitting.value = false
}
}
// 重置表单
const resetForm = () => {
Object.assign(formData, {
name: '',
email: '',
age: null
})
validationErrors.value = []
}
// 模拟表单提交
const submitForm = (data) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.2) {
resolve(data)
} else {
reject(new Error('网络错误'))
}
}, 1000)
})
}
return {
formData,
isSubmitting,
validationErrors,
handleSubmit,
resetForm
}
}
}
</script>
数据表格组件
<template>
<div class="data-table">
<div class="table-header">
<input
v-model="searchQuery"
placeholder="搜索..."
class="search-input"
/>
<select v-model="sortBy" @change="sortData">
<option value="name">按姓名排序</option>
<option value="age">按年龄排序</option>
<option value="email">按邮箱排序</option>
</select>
</div>
<table>
<thead>
<tr>
<th @click="sort('name')">姓名</th>
<th @click="sort('age')">年龄</th>
<th @click="sort('email')">邮箱</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="row in paginatedData" :key="row.id">
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.email }}</td>
<td>
<button @click="editRow(row)">编辑</button>
<button @click="deleteRow(row.id)">删除</button>
</td>
</tr>
</tbody>
</table>
<div class="pagination">
<button
:disabled="currentPage === 1"
@click="goToPage(currentPage - 1)"
>
上一页
</button>
<span>第 {{ currentPage }} 页,共 {{ totalPages }} 页</span>
<button
:disabled="currentPage === totalPages"
@click="goToPage(currentPage + 1)"
>
下一页
</button>
</div>
<div v-if="loading">加载中...</div>
</div>
</template>
<script>
import { ref, computed, watchEffect } from 'vue'
export default {
props: {
data: {
type: Array,
required: true
}
},
setup(props) {
const searchQuery = ref('')
const sortBy = ref('name')
const sortOrder = ref('asc')
const currentPage = ref(1)
const pageSize = ref(10)
const loading = ref(false)
// 搜索和排序后的数据
const filteredAndSortedData = computed(() => {
let result = [...props.data]
// 搜索过滤
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase()
result = result.filter(item =>
item.name.toLowerCase().includes(query) ||
item.email.toLowerCase().includes(query)
)
}
// 排序
result.sort((a, b) => {
const aVal = a[sortBy.value]
const bVal = b[sortBy.value]
if (sortOrder.value === 'asc') {
return aVal > bVal ? 1 : -1
} else {
return aVal < bVal ? 1 : -1
}
})
return result
})
// 分页数据
const paginatedData = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
return filteredAndSortedData.value.slice(start, end)
})
const totalPages = computed(() => {
return Math.ceil(filteredAndSortedData.value.length / pageSize.value)
})
// 排序方法
const sort = (field) => {
if (sortBy.value === field) {
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
} else {
sortBy.value = field
sortOrder.value = 'asc'
}
}
// 分页导航
const goToPage = (page) => {
if (page >= 1 && page <= totalPages.value) {
currentPage.value = page
}
}
// 数据排序和搜索实时更新
watchEffect(() => {
// 模拟异步加载数据
loading.value = true
setTimeout(() => {
loading.value = false
}, 500)
})
const editRow = (row) => {
console.log('编辑行:', row)
}
const deleteRow = (id) => {
console.log('删除行 ID:', id)
}
// 排序数据
const sortData = () => {
// 排序逻辑已通过 computed 实现
}
return {
searchQuery,
sortBy,
sortOrder,
currentPage,
pageSize,
loading,
paginatedData,
totalPages,
sort,
goToPage,
editRow,
deleteRow,
sortData
}
}
}
</script>
<style scoped>
.data-table {
padding: 20px;
}
.table-header {
display: flex;
gap: 10px;
margin-bottom: 20px;
align-items: center;
}
.search-input {
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin-top: 20px;
}
</style>
最佳实践与注意事项
代码组织原则
// 好的组织方式:按功能分组
import { ref, reactive, computed, watch } from 'vue'
export default {
setup() {
// 1. 响应式数据声明
const count = ref(0)
const user = reactive({ name: '', email: '' })
// 2. 计算属性
const displayName = computed(() => {
return user.name || 'Anonymous'
})
const isAdult = computed(() => {
return user.age >= 18
})
// 3. 本地方法
const increment = () => {
count.value++
}
const reset = () => {
count.value = 0
}
// 4. 异步操作
const fetchData = async () => {
// 异步逻辑
}
// 5. 监听器
watch(count, (newValue) => {
console.log('Count changed:', newValue)
})
return {
count,
user,
displayName,
isAdult,
increment,
reset,
fetchData
}
}
}
性能监控和调试
import { ref, computed, watch } from 'vue'
export default {
setup() {
const startTime = performance.now()
// 使用计算属性时的性能监控
const expensiveValue = computed(() => {
console.log('Computing expensive value...')
// 模拟复杂计算
let result = 0
for (let i = 0; i < 1000000; i++) {
result += Math.sqrt(i)
}
return result
})
// 监听性能变化
watch(expensiveValue, (newValue) => {
const endTime = performance.now()
console.log(`Computed value took ${(endTime - startTime).toFixed(2)}ms`)
})
// 使用 Vue DevTools 的调试工具
if (__VUE_DEVTOOLS__) {
console.log('Vue DevTools detected')
}
return {
expensiveValue
}
}
}
总结
Vue 3 Composition API 为前端开发带来了革命性的变化,它不仅提供了更灵活的代码组织方式,还极大地增强了组件间的通信能力和状态管理能力。通过合理使用 ref、reactive、computed 和 watch 等核心函数,开发者可以构建出更加模块化、可维护和高性能的应用程序。
在实际开发中,建议遵循以下原则:
- 合理的代码组织:将相关的逻辑分组,便于维护和复用
- 性能优化意识:合理使用计算属性和侦听器,避免不必要的重复计算
- **组件通信最佳实践

评论 (0)