引言
Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。相比传统的 Options API,Composition API 提供了更加灵活和强大的组件开发方式,特别是在处理复杂组件逻辑时展现出显著优势。本文将深入探讨 Vue 3 Composition API 的核心概念、使用技巧以及最佳实践,帮助开发者构建更灵活、可维护的 Vue 应用。
Vue 3 Composition API 核心概念
什么是 Composition API?
Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式。它允许开发者将组件的相关逻辑按功能进行组合,而不是按照选项(如 data、methods、computed 等)来组织代码。这种组织方式更加灵活,特别是在处理复杂组件时,能够有效避免代码分散和重复的问题。
Composition API 的核心函数
Composition API 提供了一系列响应式函数,这些函数是构建组件逻辑的基础:
ref():创建响应式数据reactive():创建响应式对象computed():创建计算属性watch():监听数据变化watchEffect():自动追踪依赖的副作用函数
响应式数据管理
Ref 的使用
ref 是最基础的响应式数据创建函数,它可以为任何类型的值创建响应式引用。让我们通过一个简单的例子来理解:
import { ref, watch } from 'vue'
export default {
setup() {
// 创建基本类型响应式数据
const count = ref(0)
const message = ref('Hello Vue 3')
// 修改数据
const increment = () => {
count.value++
}
// 监听变化
watch(count, (newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变为 ${newVal}`)
})
return {
count,
message,
increment
}
}
}
在模板中使用:
<template>
<div>
<p>计数: {{ count }}</p>
<p>消息: {{ message }}</p>
<button @click="increment">增加</button>
</div>
</template>
Reactive 的使用
reactive 用于创建响应式对象,它会将对象的所有属性都转换为响应式的:
import { reactive, watch } from 'vue'
export default {
setup() {
// 创建响应式对象
const state = reactive({
name: 'Vue',
version: '3.0',
isAwesome: true,
userInfo: {
age: 25,
city: 'Beijing'
}
})
const updateUserInfo = () => {
state.userInfo.age++
state.isAwesome = !state.isAwesome
}
// 监听对象变化
watch(state, (newVal, oldVal) => {
console.log('状态发生变化:', newVal)
}, { deep: true })
return {
state,
updateUserInfo
}
}
}
响应式数据的访问和修改
在 Composition API 中,访问响应式数据需要通过 .value 属性,而在模板中可以直接使用:
import { ref, reactive } from 'vue'
export default {
setup() {
const count = ref(0)
const user = reactive({
name: 'John',
age: 30
})
// 在 setup 中修改数据
const increment = () => {
count.value++
user.age++
}
// 返回给模板使用
return {
count,
user,
increment
}
}
}
生命周期钩子
使用生命周期钩子
Composition API 提供了与 Options API 对应的生命周期钩子函数:
import { onMounted, onUpdated, onUnmounted, ref } from 'vue'
export default {
setup() {
const count = ref(0)
// 组件挂载时执行
onMounted(() => {
console.log('组件已挂载')
// 可以在这里进行 DOM 操作
const timer = setInterval(() => {
count.value++
}, 1000)
// 返回清理函数
return () => {
clearInterval(timer)
console.log('清理定时器')
}
})
// 组件更新时执行
onUpdated(() => {
console.log('组件已更新')
})
// 组件卸载前执行
onUnmounted(() => {
console.log('组件即将卸载')
})
return {
count
}
}
}
生命周期钩子的最佳实践
import {
onMounted,
onUpdated,
onUnmounted,
watch,
ref
} from 'vue'
export default {
setup() {
const data = ref(null)
const loading = ref(false)
// 挂载时获取数据
onMounted(async () => {
await fetchData()
})
// 监听数据变化
watch(data, (newData) => {
console.log('数据更新:', newData)
})
const fetchData = async () => {
loading.value = true
try {
const response = await fetch('/api/data')
data.value = await response.json()
} catch (error) {
console.error('获取数据失败:', error)
} finally {
loading.value = false
}
}
return {
data,
loading,
fetchData
}
}
}
计算属性和监听器
Computed 的使用
computed 函数用于创建计算属性,它会自动追踪依赖并缓存结果:
import { ref, computed } from 'vue'
export default {
setup() {
const firstName = ref('John')
const lastName = ref('Doe')
const age = ref(30)
// 基础计算属性
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 isAdult = computed(() => {
return age.value >= 18
})
const userStatus = computed(() => {
if (isAdult.value) {
return '成年人'
} else {
return '未成年人'
}
})
return {
firstName,
lastName,
age,
fullName,
displayName,
isAdult,
userStatus
}
}
}
Watch 的使用
watch 函数用于监听数据变化,提供了丰富的配置选项:
import { ref, watch, watchEffect } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('Vue')
const userInfo = ref({
age: 25,
city: 'Beijing'
})
// 监听单个值
watch(count, (newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变为 ${newVal}`)
})
// 监听多个值
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log(`count: ${oldCount} -> ${newCount}, name: ${oldName} -> ${newName}`)
})
// 深度监听对象
watch(userInfo, (newVal, oldVal) => {
console.log('用户信息变化:', newVal)
}, { deep: true })
// 立即执行
watch(count, (newVal) => {
console.log('立即执行:', newVal)
}, { immediate: true })
// 使用 watchEffect 自动追踪依赖
const watchEffectExample = () => {
watchEffect(() => {
console.log(`姓名: ${name.value}, 年龄: ${userInfo.value.age}`)
// 这里会自动追踪 name 和 userInfo 的变化
})
}
return {
count,
name,
userInfo,
watchEffectExample
}
}
}
组合式函数复用
创建可复用的组合式函数
组合式函数是 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(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
console.error('获取数据失败:', err)
} finally {
loading.value = false
}
}
const refresh = () => {
fetchData()
}
return {
data,
loading,
error,
fetchData,
refresh
}
}
// composables/useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
const value = ref(defaultValue)
// 从 localStorage 初始化值
const initValue = localStorage.getItem(key)
if (initValue !== null) {
try {
value.value = JSON.parse(initValue)
} catch (e) {
console.error('解析 localStorage 失败:', e)
}
}
// 监听变化并同步到 localStorage
watch(value, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
return value
}
使用组合式函数
<template>
<div>
<h2>计数器示例</h2>
<p>当前计数: {{ count }}</p>
<p>双倍计数: {{ doubleCount }}</p>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
<button @click="reset">重置</button>
<h2>API 数据示例</h2>
<div v-if="loading">加载中...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else-if="data">
<pre>{{ JSON.stringify(data, null, 2) }}</pre>
</div>
<button @click="fetchData">获取数据</button>
<h2>本地存储示例</h2>
<input v-model="theme" placeholder="输入主题颜色">
<p>当前主题: {{ theme }}</p>
</div>
</template>
<script>
import { useCounter } from './composables/useCounter'
import { useApi } from './composables/useApi'
import { useLocalStorage } from './composables/useLocalStorage'
export default {
setup() {
// 使用计数器组合式函数
const { count, increment, decrement, reset, doubleCount } = useCounter(10)
// 使用 API 组合式函数
const { data, loading, error, fetchData } = useApi('/api/users')
// 使用本地存储组合式函数
const theme = useLocalStorage('theme', '#ffffff')
return {
count,
increment,
decrement,
reset,
doubleCount,
data,
loading,
error,
fetchData,
theme
}
}
}
</script>
复杂组件状态管理
状态管理的最佳实践
在复杂的组件中,合理的状态管理至关重要。我们可以结合组合式函数和响应式系统来构建健壮的状态管理:
// composables/useForm.js
import { ref, reactive, computed } from 'vue'
export function useForm(initialData = {}) {
const formData = reactive({ ...initialData })
const errors = reactive({})
const isSubmitting = ref(false)
// 验证规则
const validators = {}
const setValidator = (field, validator) => {
validators[field] = validator
}
const validateField = (field) => {
if (validators[field]) {
const error = validators[field](formData[field])
errors[field] = error || ''
return !error
}
return true
}
const validateAll = () => {
let isValid = true
Object.keys(validators).forEach(field => {
if (!validateField(field)) {
isValid = false
}
})
return isValid
}
const clearErrors = () => {
Object.keys(errors).forEach(key => {
errors[key] = ''
})
}
const submit = async (submitHandler) => {
if (!validateAll()) {
return false
}
isSubmitting.value = true
try {
await submitHandler(formData)
clearErrors()
return true
} catch (error) {
console.error('提交失败:', error)
return false
} finally {
isSubmitting.value = false
}
}
const reset = () => {
Object.keys(formData).forEach(key => {
formData[key] = initialData[key] || ''
})
clearErrors()
}
// 计算属性:检查是否有错误
const hasErrors = computed(() => {
return Object.values(errors).some(error => error !== '')
})
return {
formData,
errors,
isSubmitting,
hasErrors,
setValidator,
validateField,
validateAll,
submit,
reset
}
}
// composables/usePagination.js
import { ref, computed } from 'vue'
export function usePagination(totalItems = 0, itemsPerPage = 10) {
const currentPage = ref(1)
const pageSize = ref(itemsPerPage)
const totalPages = computed(() => {
return Math.ceil(totalItems / pageSize.value)
})
const startIndex = computed(() => {
return (currentPage.value - 1) * pageSize.value
})
const endIndex = computed(() => {
return Math.min(startIndex.value + pageSize.value, totalItems)
})
const hasNextPage = computed(() => {
return currentPage.value < totalPages.value
})
const hasPrevPage = computed(() => {
return currentPage.value > 1
})
const goToPage = (page) => {
if (page >= 1 && page <= totalPages.value) {
currentPage.value = page
}
}
const nextPage = () => {
if (hasNextPage.value) {
currentPage.value++
}
}
const prevPage = () => {
if (hasPrevPage.value) {
currentPage.value--
}
}
const setPageSize = (size) => {
pageSize.value = size
currentPage.value = 1
}
return {
currentPage,
pageSize,
totalPages,
startIndex,
endIndex,
hasNextPage,
hasPrevPage,
goToPage,
nextPage,
prevPage,
setPageSize
}
}
实际应用示例
<template>
<div class="form-container">
<h2>用户注册表单</h2>
<!-- 表单 -->
<form @submit.prevent="handleSubmit">
<div class="form-group">
<label>用户名:</label>
<input v-model="formData.username" type="text" />
<span class="error" v-if="errors.username">{{ errors.username }}</span>
</div>
<div class="form-group">
<label>邮箱:</label>
<input v-model="formData.email" type="email" />
<span class="error" v-if="errors.email">{{ errors.email }}</span>
</div>
<div class="form-group">
<label>密码:</label>
<input v-model="formData.password" type="password" />
<span class="error" v-if="errors.password">{{ errors.password }}</span>
</div>
<button
type="submit"
:disabled="isSubmitting || hasErrors"
class="submit-btn"
>
{{ isSubmitting ? '提交中...' : '注册' }}
</button>
</form>
<!-- 分页器 -->
<div class="pagination-container">
<h3>用户列表</h3>
<div class="user-list">
<div
v-for="user in paginatedUsers"
:key="user.id"
class="user-item"
>
{{ user.name }} - {{ user.email }}
</div>
</div>
<div class="pagination-controls">
<button @click="prevPage" :disabled="!hasPrevPage">上一页</button>
<span>第 {{ currentPage }} 页,共 {{ totalPages }} 页</span>
<button @click="nextPage" :disabled="!hasNextPage">下一页</button>
</div>
</div>
<!-- 提交结果显示 -->
<div v-if="submitSuccess" class="success-message">
注册成功!
</div>
</div>
</template>
<script>
import { usePagination } from './composables/usePagination'
import { useForm } from './composables/useForm'
export default {
setup() {
// 表单状态管理
const {
formData,
errors,
isSubmitting,
hasErrors,
setValidator,
validateField,
submit,
reset
} = useForm({
username: '',
email: '',
password: ''
})
// 设置验证规则
setValidator('username', (value) => {
if (!value) return '用户名不能为空'
if (value.length < 3) return '用户名至少3个字符'
return ''
})
setValidator('email', (value) => {
if (!value) return '邮箱不能为空'
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(value)) return '邮箱格式不正确'
return ''
})
setValidator('password', (value) => {
if (!value) return '密码不能为空'
if (value.length < 6) return '密码至少6个字符'
return ''
})
// 分页状态管理
const {
currentPage,
totalPages,
hasNextPage,
hasPrevPage,
nextPage,
prevPage,
startIndex,
endIndex
} = usePagination(50, 10)
// 模拟用户数据
const users = Array.from({ length: 50 }, (_, i) => ({
id: i + 1,
name: `用户${i + 1}`,
email: `user${i + 1}@example.com`
}))
const paginatedUsers = computed(() => {
return users.slice(startIndex.value, endIndex.value)
})
// 提交处理
const handleSubmit = async () => {
const success = await submit(async (data) => {
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 1000))
console.log('提交数据:', data)
return { success: true }
})
if (success) {
// 提交成功后的处理
reset()
}
}
const submitSuccess = ref(false)
return {
formData,
errors,
isSubmitting,
hasErrors,
currentPage,
totalPages,
hasNextPage,
hasPrevPage,
nextPage,
prevPage,
paginatedUsers,
handleSubmit,
submitSuccess
}
}
}
</script>
<style scoped>
.form-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.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;
font-size: 12px;
margin-top: 5px;
}
.submit-btn {
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.submit-btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.pagination-container {
margin-top: 30px;
padding: 20px;
border: 1px solid #eee;
border-radius: 4px;
}
.user-item {
padding: 10px;
border-bottom: 1px solid #eee;
}
.pagination-controls {
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
margin-top: 20px;
}
.success-message {
background-color: #d4edda;
color: #155724;
padding: 15px;
border-radius: 4px;
margin-top: 20px;
}
</style>
性能优化技巧
合理使用响应式系统
// 避免不必要的响应式包装
import { ref, reactive, computed } from 'vue'
export default {
setup() {
// ✅ 好的做法:只对需要响应式的值使用 ref/reactive
const count = ref(0)
const user = reactive({
name: 'John',
age: 30
})
// ❌ 避免:不必要的响应式包装
// const notNeededReactive = reactive({ value: 'simple string' })
// ✅ 好的做法:使用 computed 缓存计算结果
const expensiveCalculation = computed(() => {
// 模拟复杂计算
let result = 0
for (let i = 0; i < 1000000; i++) {
result += Math.random()
}
return result
})
return {
count,
user,
expensiveCalculation
}
}
}
避免循环依赖
// ❌ 避免:可能导致循环依赖的写法
import { ref, computed } from 'vue'
export default {
setup() {
const a = ref(1)
const b = ref(2)
// 这种写法可能导致循环依赖问题
const computedA = computed(() => {
return b.value + 1
})
const computedB = computed(() => {
return a.value + 1
})
return {
a,
b,
computedA,
computedB
}
}
}
// ✅ 好的做法:避免循环依赖
export default {
setup() {
const a = ref(1)
const b = ref(2)
// 使用简单的计算逻辑
const sum = computed(() => {
return a.value + b.value
})
return {
a,
b,
sum
}
}
}
最佳实践总结
组件结构优化
// 推荐的组件结构
import {
ref,
reactive,
computed,
watch,
onMounted,
onUnmounted
} from 'vue'
export default {
name: 'MyComponent',
props: {
title: String,
data: Object
},
setup(props, { emit }) {
// 1. 响应式数据声明
const count = ref(0)
const state = reactive({
loading: false,
error: null
})
// 2. 计算属性
const displayTitle = computed(() => {
return props.title || '默认标题'
})
const hasData = computed(() => {
return props.data && Object.keys(props.data).length > 0
})
// 3. 方法定义
const increment = () => {
count.value++
}
const handleDataUpdate = (newData) => {
emit('update:data', newData)
}
// 4. 生命周期钩子
onMounted(() => {
console.log('组件已挂载')
// 初始化逻辑
})
onUnmounted(() => {
console.log('组件即将卸载')
// 清理逻辑
})
// 5. 监听器
watch(count, (newVal) => {
console.log('计数变化:', newVal)
})
// 6. 返回给模板使用的数据和方法
return {
count,
state,
displayTitle,
hasData,
increment,
handleDataUpdate
}
}
}
错误处理和调试
import { ref, reactive, onErrorCaptured } from 'vue'
export default {
setup() {
const error = ref(null)
// 全局错误捕获
onErrorCaptured((err, instance, info) => {
console.error('组件错误:', err, info)
error.value = err.message
return false // 阻止错误继续冒泡
})
// 异步操作的错误处理
const fetchData = async () => {
try {
const response = await fetch('/api/data')
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
return await response.json()
} catch (err) {
console.error('数据获取失败:', err)
error.value = err.message
throw err // 重新抛出错误供上层处理
}
}
return {
error,
fetchData
}
}
}
结论
Vue 3 Composition API 为前端开发者提供了更强大、更灵活的组件开发方式。通过合理使用 ref、reactive、computed、watch 等核心函数,以及创建可复用的组合式函数,我们可以构建出更加清晰、可维护的组件。
在实际项目中

评论 (0)