前言
Vue 3 的发布标志着前端开发进入了一个新的时代。作为 Vue 3 的核心特性之一,Composition API 为开发者提供了更加灵活和强大的组件逻辑组织方式。与传统的 Options API 相比,Composition API 允许我们以函数的形式组织组件逻辑,使得代码更加模块化、可重用和易于维护。
本文将深入探讨 Vue 3 Composition API 的各个方面,从基础语法到复杂的状态管理实践,帮助开发者全面掌握这一强大的工具,并在实际项目中应用最佳实践。
什么是 Composition API
核心概念
Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式。它允许我们将组件的逻辑按照功能模块进行分割,而不是按照选项(如 data、methods、computed 等)来组织代码。这种设计模式使得组件更加灵活,特别是对于复杂的业务逻辑和跨组件逻辑复用场景。
与 Options API 的对比
在 Vue 2 中,我们通常使用 Options API 来组织组件逻辑:
export default {
data() {
return {
count: 0,
message: 'Hello'
}
},
computed: {
doubledCount() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
},
mounted() {
console.log('Component mounted')
}
}
而在 Vue 3 的 Composition API 中,同样的逻辑可以这样组织:
import { ref, computed, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
const message = ref('Hello')
const doubledCount = computed(() => count.value * 2)
const increment = () => {
count.value++
}
onMounted(() => {
console.log('Component mounted')
})
return {
count,
message,
doubledCount,
increment
}
}
}
响应式 API 核心概念
ref 和 reactive 的使用
在 Composition API 中,响应式数据主要通过 ref 和 reactive 来创建:
import { ref, reactive } from 'vue'
// 使用 ref 创建响应式变量
const count = ref(0)
const name = ref('Vue')
const isActive = ref(true)
// 使用 reactive 创建响应式对象
const state = reactive({
count: 0,
name: 'Vue',
isActive: true
})
// 访问值时需要使用 .value
console.log(count.value) // 0
// 修改值
count.value = 10
深层响应式对象处理
对于嵌套的对象,reactive 可以自动处理深层响应:
import { reactive } from 'vue'
const user = reactive({
profile: {
name: 'Vue',
age: 3,
address: {
city: 'Shanghai',
country: 'China'
}
},
hobbies: ['coding', 'reading']
})
// 修改深层属性
user.profile.name = 'Vue 3' // 响应式更新
user.profile.address.city = 'Beijing' // 也会触发响应式更新
readonly 和 toRefs
import { reactive, readonly, toRefs } from 'vue'
const original = reactive({ count: 0 })
const readonlyObj = readonly(original)
// readonlyObj.count = 1 // 这会抛出错误,在严格模式下
// toRefs 将响应式对象转换为 ref
const state = reactive({
name: 'Vue',
version: '3.0'
})
const { name, version } = toRefs(state)
// name 和 version 现在都是 ref 对象
生命周期钩子的使用
基本生命周期钩子
Composition API 提供了与 Vue 2 相同的生命周期钩子,但以函数形式提供:
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onRenderTracked,
onRenderTriggered
} from 'vue'
export default {
setup() {
onBeforeMount(() => {
console.log('Before mount')
})
onMounted(() => {
console.log('Mounted')
})
onBeforeUpdate(() => {
console.log('Before update')
})
onUpdated(() => {
console.log('Updated')
})
onBeforeUnmount(() => {
console.log('Before unmount')
})
onUnmounted(() => {
console.log('Unmounted')
})
// 错误处理
onErrorCaptured((error, instance, info) => {
console.error('Error captured:', error)
return false // 阻止错误继续向上传播
})
}
}
在组件中使用生命周期钩子
import { ref, onMounted, onUnmounted } from 'vue'
export default {
setup() {
const timer = ref(null)
const count = ref(0)
// 组件挂载时启动定时器
onMounted(() => {
timer.value = setInterval(() => {
count.value++
}, 1000)
})
// 组件卸载时清除定时器
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value)
}
})
return {
count
}
}
}
计算属性和监听器
computed 的使用
import { ref, computed } from 'vue'
export default {
setup() {
const firstName = ref('Vue')
const lastName = ref('3')
// 基本计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 带 getter 和 setter 的计算属性
const reversedFullName = computed({
get: () => {
return `${lastName.value} ${firstName.value}`
},
set: (value) => {
const names = value.split(' ')
firstName.value = names[0]
lastName.value = names[1]
}
})
return {
firstName,
lastName,
fullName,
reversedFullName
}
}
}
watch 和 watchEffect
import { ref, watch, watchEffect } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('Vue')
// 基本监听器
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}`)
})
// 深度监听
const state = ref({
user: {
name: 'Vue',
age: 3
}
})
watch(state, (newVal, oldVal) => {
console.log('State changed:', newVal)
}, { deep: true })
// watchEffect - 自动追踪依赖
const effect = watchEffect(() => {
console.log(`Count is: ${count.value}`)
console.log(`Name is: ${name.value}`)
})
// 清除 watchEffect
// effect.stop()
return {
count,
name
}
}
}
组件通信
父子组件通信
// Parent.vue
import { ref } from 'vue'
import Child from './Child.vue'
export default {
components: {
Child
},
setup() {
const parentMessage = ref('Hello from parent')
// 向子组件传递数据
const childData = ref({
message: 'From parent',
count: 0
})
const updateChildCount = (newCount) => {
childData.value.count = newCount
}
return {
parentMessage,
childData,
updateChildCount
}
}
}
// Child.vue
import { defineProps, defineEmits } from 'vue'
export default {
props: {
message: String,
count: Number
},
emits: ['update-count'],
setup(props, { emit }) {
const increment = () => {
const newCount = props.count + 1
emit('update-count', newCount)
}
return {
increment
}
}
}
provide 和 inject
// Parent.vue
import { provide, ref } from 'vue'
export default {
setup() {
const theme = ref('dark')
const user = ref({ name: 'Vue', role: 'developer' })
// 提供数据给后代组件
provide('theme', theme)
provide('user', user)
provide('updateTheme', (newTheme) => {
theme.value = newTheme
})
return {
theme,
user
}
}
}
// Child.vue
import { inject } from 'vue'
export default {
setup() {
// 注入数据
const theme = inject('theme')
const user = inject('user')
const updateTheme = inject('updateTheme')
const changeTheme = () => {
updateTheme(theme.value === 'dark' ? 'light' : 'dark')
}
return {
theme,
user,
changeTheme
}
}
}
复杂状态管理实践
自定义 Composable 函数
// 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/useApi.js
import { ref, reactive } from 'vue'
export function useApi() {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchData = async (url) => {
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
console.error('API Error:', err)
} finally {
loading.value = false
}
}
const clear = () => {
data.value = null
error.value = null
loading.value = false
}
return {
data,
loading,
error,
fetchData,
clear
}
}
// 使用自定义 Composable
import { useCounter } from '@/composables/useCounter'
import { useApi } from '@/composables/useApi'
export default {
setup() {
const counter = useCounter(0)
const api = useApi()
return {
...counter,
...api
}
}
}
状态管理的高级模式
// stores/userStore.js
import { ref, computed } from 'vue'
export function useUserStore() {
const users = ref([])
const currentUser = ref(null)
const loading = ref(false)
const userCount = computed(() => users.value.length)
const addUser = (user) => {
users.value.push(user)
}
const removeUser = (userId) => {
users.value = users.value.filter(user => user.id !== userId)
}
const setCurrentUser = (user) => {
currentUser.value = user
}
const fetchUsers = async () => {
try {
loading.value = true
// 模拟 API 调用
const response = await new Promise(resolve => {
setTimeout(() => {
resolve([
{ id: 1, name: 'Vue', email: 'vue@example.com' },
{ id: 2, name: 'React', email: 'react@example.com' }
])
}, 1000)
})
users.value = response
} catch (error) {
console.error('Failed to fetch users:', error)
} finally {
loading.value = false
}
}
return {
users,
currentUser,
loading,
userCount,
addUser,
removeUser,
setCurrentUser,
fetchUsers
}
}
// 在组件中使用
import { useUserStore } from '@/stores/userStore'
export default {
setup() {
const store = useUserStore()
// 初始化数据
store.fetchUsers()
return {
...store
}
}
}
性能优化技巧
使用 memoization
import { ref, computed } from 'vue'
export default {
setup() {
const items = ref([])
// 使用 computed 进行缓存计算
const expensiveCalculation = computed(() => {
console.log('Calculating...')
return items.value.reduce((sum, item) => sum + item.value, 0)
})
// 手动实现 memoization
const cachedResults = ref(new Map())
const getExpensiveResult = (key, calculationFn) => {
if (cachedResults.value.has(key)) {
return cachedResults.value.get(key)
}
const result = calculationFn()
cachedResults.value.set(key, result)
return result
}
return {
items,
expensiveCalculation,
getExpensiveResult
}
}
}
避免不必要的重新计算
import { ref, computed, watch } from 'vue'
export default {
setup() {
const firstName = ref('')
const lastName = ref('')
const age = ref(0)
// 智能地使用计算属性
const fullName = computed(() => {
// 只有当 firstName 或 lastName 改变时才重新计算
return `${firstName.value} ${lastName.value}`
})
const userSummary = computed(() => {
// 只有当 age 改变时才重新计算
return `Age: ${age.value}`
})
// 使用 watch 监听特定依赖
const nameLength = ref(0)
watch([firstName, lastName], ([newFirstName, newLastName]) => {
nameLength.value = (newFirstName + newLastName).length
})
return {
firstName,
lastName,
age,
fullName,
userSummary,
nameLength
}
}
}
错误处理和调试
统一的错误处理机制
// composables/useErrorHandler.js
import { ref, reactive } from 'vue'
export function useErrorHandler() {
const errors = ref([])
const handleAsyncError = async (asyncFn, context = '') => {
try {
return await asyncFn()
} catch (error) {
const errorInfo = {
message: error.message,
stack: error.stack,
context,
timestamp: new Date().toISOString()
}
errors.value.push(errorInfo)
console.error('Error occurred:', errorInfo)
// 可以在这里添加错误上报逻辑
// reportError(errorInfo)
throw error
}
}
const clearErrors = () => {
errors.value = []
}
return {
errors,
handleAsyncError,
clearErrors
}
}
// 在组件中使用
import { useErrorHandler } from '@/composables/useErrorHandler'
export default {
setup() {
const errorHandler = useErrorHandler()
const fetchData = async () => {
await errorHandler.handleAsyncError(
async () => {
// 可能出错的异步操作
const response = await fetch('/api/data')
return response.json()
},
'Fetching data'
)
}
return {
...errorHandler,
fetchData
}
}
}
开发者工具集成
import { ref, computed, watch } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('Vue')
// 在开发环境中添加调试信息
if (__DEV__) {
watch(count, (newVal, oldVal) => {
console.log(`Count changed: ${oldVal} -> ${newVal}`)
})
watch(name, (newVal, oldVal) => {
console.log(`Name changed: ${oldVal} -> ${newVal}`)
})
}
// 使用响应式数据的调试
const debugInfo = computed(() => {
return {
count: count.value,
name: name.value,
timestamp: Date.now()
}
})
return {
count,
name,
debugInfo
}
}
}
最佳实践总结
代码组织原则
- 按功能分组:将相关的逻辑放在同一个 Composable 中
- 单一职责:每个 Composable 应该只负责一个特定的功能
- 可复用性:设计时考虑通用性和可重用性
// 推荐的组织方式
// composables/useForm.js
export function useForm(initialData) {
const formData = reactive({ ...initialData })
const validate = () => {
// 验证逻辑
}
const reset = () => {
Object.assign(formData, initialData)
}
return {
formData,
validate,
reset
}
}
// composables/useValidation.js
export function useValidation() {
const errors = ref({})
const validateEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email)
}
return {
errors,
validateEmail
}
}
性能优化建议
- 合理使用计算属性:避免在模板中进行复杂的计算
- 及时清理副作用:在组件卸载时清除定时器、事件监听器等
- 避免重复计算:利用 computed 的缓存机制
- 按需加载:对于大型应用,考虑懒加载 Composable
测试友好性
// 测试友好的 Composable 设计
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 getCount = () => count.value
return {
count,
increment,
decrement,
reset,
getCount
}
}
// 测试示例
describe('useCounter', () => {
it('should initialize with correct value', () => {
const { count } = useCounter(5)
expect(count.value).toBe(5)
})
it('should increment correctly', () => {
const { count, increment } = useCounter(0)
increment()
expect(count.value).toBe(1)
})
})
结语
Vue 3 的 Composition API 为我们提供了一种更加灵活和强大的组件开发方式。通过合理使用响应式 API、生命周期钩子、计算属性和监听器,我们可以构建出更加可维护和可扩展的 Vue 应用。
本文涵盖了从基础语法到复杂状态管理的各个方面,包括自定义 Composable 函数的设计模式、性能优化技巧以及错误处理机制。这些实践不仅适用于小型项目,对于大型企业级应用同样具有重要价值。
在实际开发中,建议根据项目的具体需求选择合适的 API 使用方式,并始终遵循最佳实践原则。随着 Vue 生态系统的不断发展,Composition API 将继续为我们提供更多的可能性和灵活性。
记住,好的代码不仅仅是功能正确的代码,更是易于理解、维护和扩展的代码。通过深入理解和合理应用 Composition API,我们能够编写出更加优雅和高效的 Vue 应用程序。

评论 (0)