前言
Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。这一新特性不仅解决了 Vue 2 中 Options API 的局限性,还为开发者提供了更加灵活和强大的组件开发方式。本文将深入探讨 Composition API 的核心概念、使用技巧,并结合实际项目场景演示如何进行复杂组件的状态管理。
什么是 Composition API
基础概念
Composition API 是 Vue 3 提供的一种新的组件状态管理方式,它允许我们以函数的形式组织和复用逻辑代码。与传统的 Options API(选项式 API)不同,Composition API 将组件的逻辑按功能模块进行拆分,使得代码更加清晰、可维护性更强。
在 Vue 2 中,我们通常使用 data、methods、computed、watch 等选项来组织组件逻辑。而 Composition API 则将这些逻辑封装成独立的函数,通过 setup 函数进行统一管理。
与 Options API 的对比
// Vue 2 Options API
export default {
data() {
return {
count: 0,
message: 'Hello'
}
},
computed: {
doubledCount() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
},
watch: {
count(newVal, oldVal) {
console.log(`count changed from ${oldVal} to ${newVal}`)
}
}
}
// Vue 3 Composition API
import { ref, computed, watch } from 'vue'
export default {
setup() {
const count = ref(0)
const message = ref('Hello')
const doubledCount = computed(() => count.value * 2)
const increment = () => {
count.value++
}
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`)
})
return {
count,
message,
doubledCount,
increment
}
}
}
Composition API 核心特性详解
1. 响应式数据处理
ref 和 reactive 的使用
在 Composition API 中,ref 和 reactive 是两个核心的响应式工具函数。
import { ref, reactive } from 'vue'
export default {
setup() {
// 使用 ref 创建响应式数据
const count = ref(0)
const name = ref('Vue')
// 使用 reactive 创建响应式对象
const user = reactive({
firstName: 'John',
lastName: 'Doe',
age: 30
})
// 修改数据
const increment = () => {
count.value++ // 注意:访问时需要 .value
}
const updateName = () => {
name.value = 'Vue 3'
}
return {
count,
name,
user,
increment,
updateName
}
}
}
深度响应式与浅响应式
import { reactive, ref } from 'vue'
export default {
setup() {
// 深度响应式对象
const deepObj = reactive({
user: {
profile: {
name: 'Alice',
age: 25
}
}
})
// 浅响应式对象
const shallowObj = ref({
user: {
profile: {
name: 'Bob',
age: 30
}
}
})
// 深度响应式会监听所有嵌套属性的变化
const updateDeepName = () => {
deepObj.user.profile.name = 'Alice Updated'
}
// 浅响应式只监听顶层属性变化,嵌套对象需要手动处理
const updateShallowName = () => {
shallowObj.value.user.profile.name = 'Bob Updated'
}
return {
deepObj,
shallowObj,
updateDeepName,
updateShallowName
}
}
}
2. 计算属性和监听器
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: (newValue) => {
const names = newValue.split(' ')
firstName.value = names[0]
lastName.value = names[1]
}
})
// 基于多个响应式数据的计算属性
const isAdult = computed(() => {
return age.value >= 18
})
const updateAge = (newAge) => {
age.value = newAge
}
return {
firstName,
lastName,
age,
fullName,
displayName,
isAdult,
updateAge
}
}
}
watch 监听器
import { ref, watch, watchEffect } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('Vue')
const user = reactive({
profile: {
name: 'John',
age: 30
}
})
// 基础监听器
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`)
})
// 监听多个源
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log(`count: ${oldCount} -> ${newCount}, name: ${oldName} -> ${newName}`)
})
// 深度监听对象
watch(user, (newVal, oldVal) => {
console.log('user changed:', newVal)
}, { deep: true })
// 监听器选项
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`)
}, {
immediate: true, // 立即执行
deep: false // 是否深度监听
})
// watchEffect 自动追踪依赖
const watchEffectExample = () => {
watchEffect(() => {
console.log(`name: ${name.value}, count: ${count.value}`)
// 这里会自动追踪 name 和 count 的变化
})
}
return {
count,
name,
user,
watchEffectExample
}
}
}
生命周期钩子
setup 函数中的生命周期
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'
export default {
setup() {
const count = ref(0)
const message = ref('Hello')
// 挂载前
console.log('setup executed')
// 组件挂载后
onMounted(() => {
console.log('Component mounted')
// 可以在这里执行 DOM 操作
document.title = 'My Vue App'
})
// 组件更新后
onUpdated(() => {
console.log('Component updated')
})
// 组件卸载前
onUnmounted(() => {
console.log('Component will unmount')
// 清理定时器、事件监听等
})
const increment = () => {
count.value++
}
return {
count,
message,
increment
}
}
}
完整的生命周期管理
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
export default {
setup() {
const data = ref(null)
const timer = ref(null)
// 组件即将挂载前
onBeforeMount(() => {
console.log('Before mount')
})
// 组件挂载后
onMounted(() => {
console.log('Mounted')
// 初始化数据
data.value = 'Initial Data'
// 设置定时器
timer.value = setInterval(() => {
console.log('Timer tick')
}, 1000)
})
// 组件即将更新前
onBeforeUpdate(() => {
console.log('Before update')
})
// 组件更新后
onUpdated(() => {
console.log('Updated')
})
// 组件即将卸载前
onBeforeUnmount(() => {
console.log('Before unmount')
// 清理定时器
if (timer.value) {
clearInterval(timer.value)
}
})
// 组件卸载后
onUnmounted(() => {
console.log('Unmounted')
})
const updateData = () => {
data.value = 'Updated Data'
}
return {
data,
updateData
}
}
}
复杂组件状态管理实践
用户信息管理组件
// UserInfo.vue
<template>
<div class="user-info">
<h2>{{ user.name }}</h2>
<p>Age: {{ user.age }}</p>
<p>Email: {{ user.email }}</p>
<button @click="updateUser">Update User</button>
<button @click="fetchUserData">Fetch Data</button>
</div>
</template>
<script>
import { ref, reactive, computed, watch } from 'vue'
import { useStore } from 'vuex' // 或者使用 Pinia
export default {
name: 'UserInfo',
setup() {
// 响应式数据
const user = reactive({
name: '',
age: 0,
email: ''
})
const loading = ref(false)
const error = ref(null)
// 计算属性
const isAdult = computed(() => {
return user.age >= 18
})
const userInfo = computed(() => {
return {
...user,
isAdult: isAdult.value
}
})
// 监听器
watch(user, (newUser, oldUser) => {
console.log('User updated:', newUser)
}, { deep: true })
// 方法
const fetchUserData = async () => {
loading.value = true
error.value = null
try {
// 模拟 API 调用
const response = await fetch('/api/user')
const userData = await response.json()
Object.assign(user, userData)
} catch (err) {
error.value = err.message
console.error('Failed to fetch user data:', err)
} finally {
loading.value = false
}
}
const updateUser = () => {
// 模拟更新用户信息
user.age++
user.name = `Updated User ${user.age}`
}
// 初始化数据
fetchUserData()
return {
user,
loading,
error,
isAdult,
userInfo,
fetchUserData,
updateUser
}
}
}
</script>
表单验证组件
// FormValidator.vue
<template>
<form @submit="handleSubmit">
<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="password">Password:</label>
<input
id="password"
v-model="formData.password"
type="password"
:class="{ 'error': errors.password }"
/>
<span v-if="errors.password" class="error-message">{{ errors.password }}</span>
</div>
<button type="submit" :disabled="isSubmitting || !isValid">Submit</button>
</form>
</template>
<script>
import { ref, reactive, computed, watch } from 'vue'
export default {
name: 'FormValidator',
setup() {
// 表单数据
const formData = reactive({
email: '',
password: ''
})
// 错误信息
const errors = reactive({})
// 提交状态
const isSubmitting = ref(false)
// 验证规则
const validationRules = {
email: [
value => !!value || 'Email is required',
value => /\S+@\S+\.\S+/.test(value) || 'Email address is invalid'
],
password: [
value => !!value || 'Password is required',
value => value.length >= 6 || 'Password must be at least 6 characters'
]
}
// 计算属性
const isValid = computed(() => {
return Object.keys(errors).length === 0
})
// 验证表单
const validateField = (field) => {
const value = formData[field]
const rules = validationRules[field]
if (!rules) return
for (let rule of rules) {
const result = rule(value)
if (result !== true) {
errors[field] = result
return
}
}
delete errors[field]
}
// 监听表单变化
watch(formData, (newData) => {
Object.keys(newData).forEach(field => {
validateField(field)
})
}, { deep: true })
// 提交表单
const handleSubmit = async (event) => {
event.preventDefault()
if (!isValid.value) return
isSubmitting.value = true
try {
// 模拟提交
await new Promise(resolve => setTimeout(resolve, 1000))
console.log('Form submitted:', formData)
alert('Form submitted successfully!')
} catch (error) {
console.error('Submit error:', error)
} finally {
isSubmitting.value = false
}
}
return {
formData,
errors,
isValid,
isSubmitting,
handleSubmit
}
}
}
</script>
<style scoped>
.form-group {
margin-bottom: 1rem;
}
.error {
border-color: #ff4757;
}
.error-message {
color: #ff4757;
font-size: 0.8rem;
}
</style>
组合式函数(Composables)最佳实践
创建可复用的组合式函数
// 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() {
const loading = ref(false)
const error = ref(null)
const data = ref(null)
const fetchData = async (url) => {
loading.value = true
error.value = null
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const result = await response.json()
data.value = result
} catch (err) {
error.value = err.message
console.error('API error:', err)
} finally {
loading.value = false
}
}
const clear = () => {
data.value = null
error.value = null
}
return {
loading,
error,
data,
fetchData,
clear
}
}
// 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
}
使用组合式函数的组件示例
<!-- Counter.vue -->
<template>
<div class="counter">
<h2>Counter: {{ count }}</h2>
<p>Double: {{ doubleCount }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">Reset</button>
</div>
</template>
<script>
import { useCounter } from '@/composables/useCounter'
export default {
setup() {
const { count, increment, decrement, reset, doubleCount } = useCounter(0)
return {
count,
increment,
decrement,
reset,
doubleCount
}
}
}
</script>
<!-- ApiComponent.vue -->
<template>
<div class="api-component">
<button @click="fetchData">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 {
setup() {
const { loading, error, data, fetchData } = useApi()
return {
loading,
error,
data,
fetchData
}
}
}
</script>
高级特性与最佳实践
异步数据加载优化
import { ref, reactive, watch, onMounted, onUnmounted } from 'vue'
export default {
setup() {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const abortController = ref(null)
// 防抖函数
const debounce = (func, wait) => {
let timeout
return (...args) => {
clearTimeout(timeout)
timeout = setTimeout(() => func.apply(this, args), wait)
}
}
// 带防抖的数据加载
const debouncedFetchData = debounce(async (url) => {
if (abortController.value) {
abortController.value.abort()
}
abortController.value = new AbortController()
try {
loading.value = true
error.value = null
const response = await fetch(url, {
signal: abortController.value.signal
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const result = await response.json()
data.value = result
} catch (err) {
if (err.name !== 'AbortError') {
error.value = err.message
}
} finally {
loading.value = false
}
}, 300)
// 取消请求
const cancelRequest = () => {
if (abortController.value) {
abortController.value.abort()
}
}
// 组件卸载时清理
onUnmounted(() => {
cancelRequest()
})
return {
data,
loading,
error,
debouncedFetchData,
cancelRequest
}
}
}
性能优化技巧
import { ref, computed, watch, shallowRef, markRaw } from 'vue'
export default {
setup() {
// 使用 shallowRef 进行浅层响应式
const shallowData = shallowRef({
user: { name: 'John' },
items: [1, 2, 3]
})
// 使用 markRaw 避免不必要的响应式转换
const rawData = {
apiClient: {
get: () => {},
post: () => {}
}
}
const processedData = markRaw(rawData)
// 计算属性缓存优化
const expensiveComputation = computed(() => {
// 复杂计算逻辑
return Array.from({ length: 1000 }, (_, i) => i * 2)
})
// 监听器优化
const watchOptions = {
flush: 'post', // 在组件更新后执行
deep: true,
immediate: false
}
const handleDataChange = (newVal, oldVal) => {
console.log('Data changed:', newVal)
}
return {
shallowData,
processedData,
expensiveComputation,
handleDataChange
}
}
}
错误处理和边界情况
import { ref, reactive, onErrorCaptured, onUnmounted } from 'vue'
export default {
setup() {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
// 全局错误捕获
onErrorCaptured((err, instance, info) => {
console.error('Error captured:', err, info)
return false // 阻止错误继续传播
})
// 优雅降级处理
const safeFetch = async (url) => {
try {
loading.value = true
error.value = null
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const result = await response.json()
data.value = result
return result
} catch (err) {
console.error('Fetch error:', err)
error.value = `Failed to load data: ${err.message}`
// 提供默认数据
data.value = getDefaultData()
return null
} finally {
loading.value = false
}
}
const getDefaultData = () => ({
message: 'Default data loaded',
timestamp: Date.now()
})
const retryFetch = () => {
// 重试逻辑
if (error.value) {
safeFetch('/api/data')
}
}
return {
data,
loading,
error,
safeFetch,
retryFetch
}
}
}
总结
Vue 3 的 Composition API 为前端开发者提供了更加灵活和强大的组件开发方式。通过本文的详细介绍,我们可以看到:
- 响应式数据处理:
ref和reactive提供了丰富的响应式能力 - 生命周期管理:
onMounted、onUpdated等钩子函数帮助我们精确控制组件生命周期 - 组合式函数:通过自定义组合式函数实现逻辑复用,提高代码可维护性
- 性能优化:合理使用
computed、watch选项和shallowRef等工具优化性能 - 错误处理:完善的错误捕获和降级机制确保应用稳定性
在实际项目中,建议根据具体需求选择合适的 API 风格。对于简单组件,可以继续使用 Options API;对于复杂组件,Composition API 能够提供更好的组织方式和复用能力。同时,合理使用组合式函数可以让代码更加模块化,提高开发效率。
通过掌握这些核心技术,开发者能够构建出更加健壮、可维护的 Vue 3 应用程序,充分利用现代前端开发的最佳实践。

评论 (0)