引言
Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。作为 Vue 3 的核心特性之一,Composition API 为开发者提供了一种更加灵活和强大的组件开发方式,特别是在处理复杂组件逻辑时表现尤为突出。
在 Vue 2 中,我们主要使用 Options API 来组织组件逻辑,这种方式虽然简单直观,但在面对复杂的组件时容易出现代码分散、难以维护的问题。而 Composition API 则通过将相关逻辑组合在一起的方式,让组件的结构更加清晰,同时也提供了更好的逻辑复用能力。
本文将从基础语法开始,逐步深入到复杂组件的状态管理、生命周期钩子、响应式编程等高级特性,帮助开发者全面掌握 Vue 3 Composition API 的使用方法。
什么是 Composition API
基本概念
Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式。它允许我们通过组合函数的方式来组织和复用组件逻辑,而不是传统的选项式 API(Options API)。
在传统的 Options API 中,组件的逻辑被分散到不同的选项中:data、methods、computed、watch 等。而 Composition API 则将相关的逻辑集中在一起,使得代码更加模块化和可维护。
与 Options API 的对比
为了更好地理解 Composition API 的优势,我们先来看一个简单的对比示例:
Options API 写法:
export default {
data() {
return {
count: 0,
name: 'Vue'
}
},
computed: {
message() {
return `Hello ${this.name}!`
}
},
methods: {
increment() {
this.count++
}
},
watch: {
count(newVal, oldVal) {
console.log(`count changed from ${oldVal} to ${newVal}`)
}
}
}
Composition API 写法:
import { ref, computed, watch } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('Vue')
const message = computed(() => `Hello ${name.value}!`)
const increment = () => {
count.value++
}
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`)
})
return {
count,
name,
message,
increment
}
}
}
从上面的对比可以看出,Composition API 将相关的逻辑组织在一起,代码更加清晰易读。
Composition API 基础语法
setup 函数
setup 是 Composition API 的入口函数,它在组件实例创建之前执行。所有的 Composition API 函数都必须在 setup 函数中调用。
import { ref, reactive } from 'vue'
export default {
setup() {
// 在这里使用 Composition API
const count = ref(0)
const user = reactive({
name: 'John',
age: 25
})
return {
count,
user
}
}
}
响应式数据:ref 和 reactive
在 Composition API 中,我们使用 ref 和 reactive 来创建响应式数据。
ref 的使用
ref 用于创建基本类型的响应式数据:
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('Vue')
const isActive = ref(true)
// 访问值时需要使用 .value
console.log(count.value) // 0
// 修改值
count.value = 10
return {
count,
name,
isActive
}
}
}
reactive 的使用
reactive 用于创建对象类型的响应式数据:
import { reactive } from 'vue'
export default {
setup() {
const user = reactive({
name: 'John',
age: 25,
address: {
city: 'Beijing',
country: 'China'
}
})
// 修改属性
user.name = 'Jane'
user.address.city = 'Shanghai'
return {
user
}
}
}
computed 和 watch
computed 的使用
computed 用于创建计算属性:
import { ref, computed } from 'vue'
export default {
setup() {
const firstName = ref('John')
const lastName = ref('Doe')
// 基本用法
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 带 getter 和 setter 的计算属性
const reversedName = computed({
get: () => {
return firstName.value.split('').reverse().join('')
},
set: (newValue) => {
firstName.value = newValue.split('').reverse().join('')
}
})
return {
firstName,
lastName,
fullName,
reversedName
}
}
}
watch 的使用
watch 用于监听响应式数据的变化:
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 user = reactive({
profile: {
name: 'John',
age: 25
}
})
watch(user, (newVal, oldVal) => {
console.log('user changed:', newVal)
}, { deep: true })
// 立即执行的监听器
watch(count, (newVal) => {
console.log(`count is now ${newVal}`)
}, { immediate: true })
return {
count,
name
}
}
}
watchEffect
watchEffect 是一个更简洁的监听函数,它会自动追踪其内部使用的响应式数据:
import { ref, watchEffect } from 'vue'
export default {
setup() {
const count = ref(0)
// watchEffect 会立即执行,并自动追踪依赖
const stop = watchEffect(() => {
console.log(`count is: ${count.value}`)
})
// 停止监听
// stop()
return {
count
}
}
}
组件生命周期钩子
生命周期钩子函数
Composition API 提供了与 Vue 2 相同的生命周期钩子,但以函数的形式提供:
import {
onMounted,
onUpdated,
onUnmounted,
onBeforeMount,
onBeforeUpdate,
onBeforeUnmount
} from 'vue'
export default {
setup() {
// 组件挂载时调用
onMounted(() => {
console.log('Component mounted')
})
// 组件更新时调用
onUpdated(() => {
console.log('Component updated')
})
// 组件卸载前调用
onBeforeUnmount(() => {
console.log('Component will unmount')
})
return {}
}
}
实际应用示例
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
}
}
}
复杂组件状态管理
状态管理的最佳实践
在复杂的组件中,合理的状态管理至关重要。我们可以通过组合多个响应式数据来构建复杂的状态:
import { ref, reactive, computed } from 'vue'
export default {
setup() {
// 用户相关状态
const user = reactive({
id: null,
name: '',
email: '',
avatar: ''
})
// 加载状态
const loading = ref(false)
// 错误状态
const error = ref(null)
// 表单数据
const formData = reactive({
name: '',
email: '',
password: ''
})
// 计算属性
const isValidForm = computed(() => {
return formData.name && formData.email && formData.password
})
const isUserLoggedIn = computed(() => {
return !!user.id
})
// 方法
const login = async () => {
loading.value = true
error.value = null
try {
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 1000))
user.id = 1
user.name = formData.name
user.email = formData.email
console.log('Login successful')
} catch (err) {
error.value = 'Login failed'
console.error(err)
} finally {
loading.value = false
}
}
const logout = () => {
user.id = null
user.name = ''
user.email = ''
user.avatar = ''
formData.name = ''
formData.email = ''
formData.password = ''
}
return {
user,
loading,
error,
formData,
isValidForm,
isUserLoggedIn,
login,
logout
}
}
}
多层级状态管理
对于更加复杂的组件,我们可以将状态组织成多个模块:
import { ref, reactive, computed } from 'vue'
export default {
setup() {
// 订单状态
const orderState = reactive({
items: [],
total: 0,
status: 'pending'
})
// 用户偏好设置
const preferences = reactive({
theme: 'light',
notifications: true,
language: 'zh-CN'
})
// 加载状态
const loadingStates = reactive({
orderList: false,
orderDetails: false,
cart: false
})
// 计算属性
const cartTotal = computed(() => {
return orderState.items.reduce((total, item) => {
return total + (item.price * item.quantity)
}, 0)
})
const hasItemsInCart = computed(() => {
return orderState.items.length > 0
})
// 方法
const addToCart = (product) => {
const existingItem = orderState.items.find(item => item.id === product.id)
if (existingItem) {
existingItem.quantity += 1
} else {
orderState.items.push({
id: product.id,
name: product.name,
price: product.price,
quantity: 1
})
}
}
const removeFromCart = (productId) => {
orderState.items = orderState.items.filter(item => item.id !== productId)
}
const updateQuantity = (productId, newQuantity) => {
const item = orderState.items.find(item => item.id === productId)
if (item) {
item.quantity = Math.max(0, newQuantity)
if (item.quantity === 0) {
removeFromCart(productId)
}
}
}
return {
orderState,
preferences,
loadingStates,
cartTotal,
hasItemsInCart,
addToCart,
removeFromCart,
updateQuantity
}
}
}
组件间通信
父子组件通信
在 Composition API 中,父子组件通信依然可以使用传统的 props 和 emit 方式:
// 父组件
import { ref } from 'vue'
export default {
setup() {
const message = ref('Hello from parent')
const handleChildEvent = (data) => {
console.log('Received from child:', data)
}
return {
message,
handleChildEvent
}
}
}
<!-- 父组件模板 -->
<template>
<child-component
:message="message"
@child-event="handleChildEvent"
/>
</template>
// 子组件
export default {
props: {
message: String
},
emits: ['child-event'],
setup(props, { emit }) {
const handleClick = () => {
emit('child-event', 'Hello from child')
}
return {
handleClick
}
}
}
非父子组件通信
对于非父子组件间的通信,我们可以使用全局状态管理:
// store.js
import { reactive } from 'vue'
export const useGlobalStore = () => {
const state = reactive({
theme: 'light',
language: 'zh-CN'
})
const setTheme = (theme) => {
state.theme = theme
}
const setLanguage = (language) => {
state.language = language
}
return {
state,
setTheme,
setLanguage
}
}
// 在组件中使用
import { useGlobalStore } from './store'
export default {
setup() {
const { state, setTheme } = useGlobalStore()
const changeTheme = () => {
setTheme(state.theme === 'light' ? 'dark' : 'light')
}
return {
state,
changeTheme
}
}
}
高级特性与最佳实践
自定义组合函数
自定义组合函数是 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
}
}
// 在组件中使用
import { useCounter } from '@/composables/useCounter'
export default {
setup() {
const {
count,
increment,
decrement,
reset,
doubleCount
} = useCounter(10)
return {
count,
increment,
decrement,
reset,
doubleCount
}
}
}
异步数据获取
处理异步数据是现代前端开发中的常见需求:
import { ref, onMounted } from 'vue'
export default {
setup() {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchData = async () => {
loading.value = true
error.value = null
try {
// 模拟 API 调用
const response = await fetch('/api/data')
data.value = await response.json()
} catch (err) {
error.value = err.message
console.error('Failed to fetch data:', err)
} finally {
loading.value = false
}
}
onMounted(() => {
fetchData()
})
return {
data,
loading,
error,
fetchData
}
}
}
性能优化
在使用 Composition API 时,需要注意性能优化:
import { ref, computed, watch, onUnmounted } from 'vue'
export default {
setup() {
const count = ref(0)
// 使用计算属性缓存复杂计算
const expensiveValue = computed(() => {
// 模拟复杂的计算
let result = 0
for (let i = 0; i < 1000000; i++) {
result += Math.sqrt(i)
}
return result + count.value
})
// 监听器的优化
const debouncedWatch = (source, cb, delay = 300) => {
let timeout
return watch(source, (...args) => {
clearTimeout(timeout)
timeout = setTimeout(() => cb(...args), delay)
})
}
// 使用防抖监听器
debouncedWatch(count, (newVal) => {
console.log('Count changed:', newVal)
})
return {
count,
expensiveValue
}
}
}
实际项目应用案例
用户管理组件示例
让我们来看一个完整的用户管理组件的实际应用:
<template>
<div class="user-management">
<div class="header">
<h2>用户管理</h2>
<button @click="showCreateForm = true">添加用户</button>
</div>
<!-- 用户列表 -->
<div v-if="!showCreateForm" class="user-list">
<div
v-for="user in paginatedUsers"
:key="user.id"
class="user-card"
>
<div class="user-info">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
<div class="user-actions">
<button @click="editUser(user)">编辑</button>
<button @click="deleteUser(user.id)">删除</button>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<button
@click="currentPage--"
:disabled="currentPage === 1"
>
上一页
</button>
<span>{{ currentPage }} / {{ totalPages }}</span>
<button
@click="currentPage++"
:disabled="currentPage === totalPages"
>
下一页
</button>
</div>
</div>
<!-- 创建/编辑表单 -->
<div v-else class="user-form">
<h3>{{ editingUser ? '编辑用户' : '添加用户' }}</h3>
<form @submit.prevent="saveUser">
<input
v-model="formData.name"
placeholder="姓名"
required
/>
<input
v-model="formData.email"
type="email"
placeholder="邮箱"
required
/>
<input
v-model="formData.phone"
placeholder="电话"
/>
<button type="submit">保存</button>
<button type="button" @click="showCreateForm = false">取消</button>
</form>
</div>
</div>
</template>
<script>
import {
ref,
reactive,
computed,
watch,
onMounted
} from 'vue'
export default {
name: 'UserManagement',
setup() {
// 状态管理
const users = ref([])
const loading = ref(false)
const error = ref(null)
// 表单状态
const showCreateForm = ref(false)
const editingUser = ref(null)
// 分页状态
const currentPage = ref(1)
const pageSize = ref(10)
// 表单数据
const formData = reactive({
name: '',
email: '',
phone: ''
})
// 计算属性
const totalPages = computed(() => {
return Math.ceil(users.value.length / pageSize.value)
})
const paginatedUsers = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
return users.value.slice(start, end)
})
// 方法
const fetchUsers = async () => {
loading.value = true
error.value = null
try {
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 1000))
users.value = [
{ id: 1, name: '张三', email: 'zhangsan@example.com', phone: '13800138000' },
{ id: 2, name: '李四', email: 'lisi@example.com', phone: '13800138001' },
{ id: 3, name: '王五', email: 'wangwu@example.com', phone: '13800138002' }
]
} catch (err) {
error.value = '获取用户列表失败'
console.error(err)
} finally {
loading.value = false
}
}
const editUser = (user) => {
editingUser.value = user
Object.assign(formData, user)
showCreateForm.value = true
}
const deleteUser = async (userId) => {
if (confirm('确定要删除这个用户吗?')) {
try {
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 500))
users.value = users.value.filter(user => user.id !== userId)
console.log(`用户 ${userId} 已删除`)
} catch (err) {
error.value = '删除用户失败'
console.error(err)
}
}
}
const saveUser = async () => {
loading.value = true
try {
if (editingUser.value) {
// 更新用户
const index = users.value.findIndex(user => user.id === editingUser.value.id)
if (index !== -1) {
users.value[index] = { ...users.value[index], ...formData }
}
} else {
// 创建新用户
const newUser = {
id: Date.now(),
...formData
}
users.value.push(newUser)
}
resetForm()
console.log('用户保存成功')
} catch (err) {
error.value = '保存用户失败'
console.error(err)
} finally {
loading.value = false
}
}
const resetForm = () => {
showCreateForm.value = false
editingUser.value = null
Object.assign(formData, { name: '', email: '', phone: '' })
}
// 监听分页变化
watch(currentPage, () => {
console.log(`切换到第 ${currentPage.value} 页`)
})
// 组件挂载时获取数据
onMounted(() => {
fetchUsers()
})
return {
users,
loading,
error,
showCreateForm,
editingUser,
currentPage,
pageSize,
totalPages,
paginatedUsers,
formData,
fetchUsers,
editUser,
deleteUser,
saveUser,
resetForm
}
}
}
</script>
<style scoped>
.user-management {
padding: 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.user-list {
margin-bottom: 20px;
}
.user-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border: 1px solid #ddd;
margin-bottom: 10px;
border-radius: 4px;
}
.user-info h3 {
margin: 0 0 5px 0;
}
.user-info p {
margin: 0;
color: #666;
}
.user-actions button {
margin-left: 10px;
padding: 5px 10px;
border: none;
border-radius: 3px;
cursor: pointer;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-top: 20px;
}
.user-form {
padding: 20px;
border: 1px solid #ddd;
border-radius: 4px;
}
.user-form form {
display: flex;
flex-direction: column;
gap: 10px;
}
.user-form input {
padding: 8px;
border: 1px solid #ddd;
border-radius: 3px;
}
.user-form button {
padding: 10px;
border: none;
border-radius: 3px;
cursor: pointer;
}
</style>
总结
Vue 3 Composition API 的引入为前端开发带来了革命性的变化。通过本文的详细介绍,我们看到了 Composition API 在以下几个方面的重要优势:
主要优势
- 更好的逻辑组织:将相关的逻辑集中在一起,避免了 Options API 中代码分散的问题
- 更强的复用能力:通过自定义组合函数,可以轻松地在不同组件间复用逻辑
- 更灵活的开发方式:开发者可以根据需要自由组织组件逻辑
- 更好的 TypeScript 支持:Composition API 与 TypeScript 的集成更加自然
最佳实践建议
- 合理使用响应式数据:根据数据类型选择
ref或reactive - 善用计算属性:利用
computed进行复杂计算的缓存 - 正确的生命周期管理:合理使用生命周期钩子函数
- 组件化思维:将复杂逻辑拆分成多个可复用的组合函数
未来展望
随着 Vue 生态系统的不断发展,Composition API 必将成为现代 Vue 开发的标准。它不仅适用于简单的组件开发,更在大型应用的状态管理和逻辑复用方面展现出巨大优势。
掌握 Composition API 不仅能帮助我们编写更加清晰、可维护的代码,还能让我们更好地适应前端技术的发展趋势。建议开发者在项目中积极尝试和应用 Composition API,逐步将其融入到日常开发工作中。
通过本文的学习,相信读者已经对 Vue 3 Composition API 有了全面深入的理解,并能够在实际项目中灵活运用这些知识来构建高质量的前端应用。

评论 (0)