引言
Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。相比于传统的 Options API,Composition API 提供了更灵活、更强大的组件开发方式,特别是在处理复杂逻辑和代码复用方面表现卓越。本文将深入探讨 Composition API 的核心概念,并通过实际项目案例演示如何构建可复用、可维护的响应式组件。
什么是 Composition API
核心概念
Composition API 是 Vue 3 中引入的一种新的组件开发模式,它允许开发者以函数的形式组织和重用组件逻辑。与传统的 Options API(基于选项的对象)不同,Composition API 将组件的逻辑按照功能进行拆分,使得代码更加模块化和可复用。
主要优势
- 更好的逻辑复用:通过组合函数实现跨组件的逻辑共享
- 更灵活的代码组织:按功能而非选项来组织代码
- 更强的类型支持:与 TypeScript 集成更好
- 更清晰的生命周期管理:更直观地处理组件生命周期
基础概念详解
reactive 和 ref 的区别
在 Composition API 中,reactive 和 ref 是两个核心的响应式数据创建函数:
import { reactive, ref } from 'vue'
// ref 用于基本类型数据
const count = ref(0)
console.log(count.value) // 0
count.value = 1
console.log(count.value) // 1
// reactive 用于对象类型数据
const state = reactive({
name: 'Vue',
version: 3
})
console.log(state.name) // Vue
state.name = 'Vue 3'
console.log(state.name) // Vue 3
生命周期钩子
Composition API 提供了与 Options API 对应的生命周期钩子:
import { onMounted, onUpdated, onUnmounted } from 'vue'
export default {
setup() {
onMounted(() => {
console.log('组件已挂载')
})
onUpdated(() => {
console.log('组件已更新')
})
onUnmounted(() => {
console.log('组件已卸载')
})
return {}
}
}
实际项目案例:构建一个任务管理组件
需求分析
让我们通过一个具体的任务管理应用来演示 Composition API 的使用。这个应用需要实现以下功能:
- 显示任务列表
- 添加新任务
- 标记任务完成状态
- 删除任务
- 搜索和过滤任务
- 本地存储任务数据
基础组件结构
首先,我们创建一个基础的任务管理组件:
<template>
<div class="task-manager">
<h2>任务管理器</h2>
<!-- 添加任务表单 -->
<form @submit.prevent="addTask" class="add-task-form">
<input
v-model="newTask"
type="text"
placeholder="添加新任务..."
class="task-input"
/>
<button type="submit" class="add-btn">添加</button>
</form>
<!-- 任务列表 -->
<div class="task-list">
<div
v-for="task in filteredTasks"
:key="task.id"
class="task-item"
>
<input
type="checkbox"
v-model="task.completed"
class="task-checkbox"
/>
<span :class="{ completed: task.completed }">
{{ task.text }}
</span>
<button @click="deleteTask(task.id)" class="delete-btn">删除</button>
</div>
</div>
<!-- 统计信息 -->
<div class="stats">
<p>总计: {{ tasks.length }} 个任务</p>
<p>已完成: {{ completedTasks }} 个任务</p>
<p>未完成: {{ remainingTasks }} 个任务</p>
</div>
</div>
</template>
<script>
import { ref, computed, onMounted } from 'vue'
import { useTaskStore } from './stores/taskStore'
export default {
name: 'TaskManager',
setup() {
// 响应式数据
const newTask = ref('')
const tasks = ref([])
// 组合函数
const taskStore = useTaskStore()
// 计算属性
const filteredTasks = computed(() => {
return tasks.value.filter(task =>
task.text.toLowerCase().includes(searchTerm.value.toLowerCase())
)
})
const completedTasks = computed(() => {
return tasks.value.filter(task => task.completed).length
})
const remainingTasks = computed(() => {
return tasks.value.length - completedTasks.value
})
// 方法
const addTask = () => {
if (newTask.value.trim()) {
const task = {
id: Date.now(),
text: newTask.value.trim(),
completed: false
}
tasks.value.push(task)
newTask.value = ''
}
}
const deleteTask = (id) => {
tasks.value = tasks.value.filter(task => task.id !== id)
}
// 生命周期钩子
onMounted(() => {
// 从本地存储加载数据
const savedTasks = localStorage.getItem('tasks')
if (savedTasks) {
tasks.value = JSON.parse(savedTasks)
}
})
return {
newTask,
tasks,
filteredTasks,
completedTasks,
remainingTasks,
addTask,
deleteTask
}
}
}
</script>
<style scoped>
.task-manager {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.add-task-form {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.task-input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.add-btn {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.task-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
border-bottom: 1px solid #eee;
}
.task-checkbox {
margin-right: 10px;
}
.completed {
text-decoration: line-through;
color: #999;
}
.delete-btn {
margin-left: auto;
padding: 5px 10px;
background-color: #dc3545;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.stats {
margin-top: 20px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 4px;
}
</style>
组合函数封装最佳实践
创建可复用的组合函数
为了提高代码的可复用性,我们将任务管理相关的逻辑封装成组合函数:
// composables/useTasks.js
import { ref, computed } from 'vue'
export function useTasks() {
const tasks = ref([])
const searchTerm = ref('')
// 加载任务
const loadTasks = () => {
const savedTasks = localStorage.getItem('tasks')
if (savedTasks) {
tasks.value = JSON.parse(savedTasks)
}
}
// 保存任务
const saveTasks = () => {
localStorage.setItem('tasks', JSON.stringify(tasks.value))
}
// 添加任务
const addTask = (text) => {
const task = {
id: Date.now(),
text,
completed: false,
createdAt: new Date()
}
tasks.value.push(task)
saveTasks()
}
// 删除任务
const deleteTask = (id) => {
tasks.value = tasks.value.filter(task => task.id !== id)
saveTasks()
}
// 切换任务完成状态
const toggleTask = (id) => {
const task = tasks.value.find(task => task.id === id)
if (task) {
task.completed = !task.completed
saveTasks()
}
}
// 过滤任务
const filteredTasks = computed(() => {
if (!searchTerm.value) return tasks.value
return tasks.value.filter(task =>
task.text.toLowerCase().includes(searchTerm.value.toLowerCase())
)
})
// 统计信息
const completedTasks = computed(() => {
return tasks.value.filter(task => task.completed).length
})
const remainingTasks = computed(() => {
return tasks.value.length - completedTasks.value
})
const allTasksCompleted = computed(() => {
return tasks.value.length > 0 && tasks.value.every(task => task.completed)
})
return {
tasks,
searchTerm,
filteredTasks,
completedTasks,
remainingTasks,
allTasksCompleted,
loadTasks,
addTask,
deleteTask,
toggleTask
}
}
使用组合函数
在组件中使用封装好的组合函数:
<template>
<div class="task-manager">
<h2>任务管理器</h2>
<!-- 搜索框 -->
<div class="search-container">
<input
v-model="searchTerm"
type="text"
placeholder="搜索任务..."
class="search-input"
/>
</div>
<!-- 添加任务表单 -->
<form @submit.prevent="handleAddTask" class="add-task-form">
<input
v-model="newTaskInput"
type="text"
placeholder="添加新任务..."
class="task-input"
/>
<button type="submit" class="add-btn">添加</button>
</form>
<!-- 任务列表 -->
<div class="task-list">
<div
v-for="task in filteredTasks"
:key="task.id"
class="task-item"
>
<input
type="checkbox"
v-model="task.completed"
@change="toggleTask(task.id)"
class="task-checkbox"
/>
<span :class="{ completed: task.completed }">
{{ task.text }}
</span>
<button @click="deleteTask(task.id)" class="delete-btn">删除</button>
</div>
<div v-if="filteredTasks.length === 0" class="no-tasks">
暂无任务
</div>
</div>
<!-- 统计信息 -->
<div class="stats">
<p>总计: {{ tasks.length }} 个任务</p>
<p>已完成: {{ completedTasks }} 个任务</p>
<p>未完成: {{ remainingTasks }} 个任务</p>
<p v-if="allTasksCompleted">🎉 所有任务已完成!</p>
</div>
</div>
</template>
<script>
import { ref, computed } from 'vue'
import { useTasks } from '../composables/useTasks'
export default {
name: 'TaskManager',
setup() {
// 使用组合函数
const {
tasks,
searchTerm,
filteredTasks,
completedTasks,
remainingTasks,
allTasksCompleted,
loadTasks,
addTask,
deleteTask,
toggleTask
} = useTasks()
const newTaskInput = ref('')
// 加载任务数据
loadTasks()
// 处理添加任务
const handleAddTask = () => {
if (newTaskInput.value.trim()) {
addTask(newTaskInput.value.trim())
newTaskInput.value = ''
}
}
return {
searchTerm,
tasks,
filteredTasks,
completedTasks,
remainingTasks,
allTasksCompleted,
newTaskInput,
handleAddTask,
deleteTask,
toggleTask
}
}
}
</script>
<style scoped>
.task-manager {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.search-container {
margin-bottom: 20px;
}
.search-input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.add-task-form {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.task-input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.add-btn {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.task-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
border-bottom: 1px solid #eee;
}
.task-checkbox {
margin-right: 10px;
}
.completed {
text-decoration: line-through;
color: #999;
}
.delete-btn {
margin-left: auto;
padding: 5px 10px;
background-color: #dc3545;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.stats {
margin-top: 20px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 4px;
}
.no-tasks {
text-align: center;
color: #999;
padding: 20px;
}
</style>
高级特性与最佳实践
响应式数据的深层理解
在使用 Composition API 时,理解响应式的本质非常重要:
import { ref, reactive, toRefs } from 'vue'
// ref 的响应式特性
const count = ref(0)
const doubled = computed(() => count.value * 2)
// reactive 的响应式特性
const state = reactive({
count: 0,
name: 'Vue'
})
// 使用 toRefs 解构响应式对象
const useCounter = () => {
const count = ref(0)
const increment = () => count.value++
// 返回解构后的响应式数据
return {
...toRefs({ count }),
increment
}
}
异步操作处理
处理异步操作时,需要特别注意响应式的更新:
import { ref, watch } from 'vue'
export function useAsyncData() {
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('Fetch error:', err)
} finally {
loading.value = false
}
}
return {
data,
loading,
error,
fetchData
}
}
组件间通信
通过组合函数实现组件间的逻辑共享:
// composables/useGlobalState.js
import { ref, watch } from 'vue'
// 全局状态管理
const globalState = ref({
theme: 'light',
language: 'zh-CN'
})
export function useGlobalState() {
const getTheme = () => globalState.value.theme
const setTheme = (theme) => {
globalState.value.theme = theme
localStorage.setItem('theme', theme)
}
const getLanguage = () => globalState.value.language
const setLanguage = (language) => {
globalState.value.language = language
localStorage.setItem('language', language)
}
// 监听状态变化
watch(globalState, (newVal) => {
console.log('Global state changed:', newVal)
})
return {
theme: computed(() => globalState.value.theme),
language: computed(() => globalState.value.language),
setTheme,
setLanguage
}
}
性能优化策略
计算属性的合理使用
import { computed, ref } from 'vue'
export function useOptimizedTasks() {
const tasks = ref([])
// 避免在计算属性中进行复杂操作
const filteredTasks = computed(() => {
// 简单的过滤操作
return tasks.value.filter(task => task.completed)
})
// 复杂计算使用缓存
const expensiveCalculation = computed(() => {
// 只有当依赖项改变时才重新计算
return tasks.value.reduce((acc, task) => {
if (task.completed) {
acc.completedCount++
acc.completedItems.push(task.text)
}
return acc
}, { completedCount: 0, completedItems: [] })
})
// 避免不必要的重新计算
const getTaskById = (id) => {
return computed(() => tasks.value.find(task => task.id === id))
}
return {
tasks,
filteredTasks,
expensiveCalculation,
getTaskById
}
}
组件懒加载和性能监控
import { onMounted, onUnmounted } from 'vue'
export function usePerformanceMonitoring() {
const startTime = ref(0)
const endTime = ref(0)
const startTimer = () => {
startTime.value = performance.now()
}
const endTimer = () => {
endTime.value = performance.now()
console.log(`Component rendered in ${(endTime.value - startTime.value).toFixed(2)}ms`)
}
onMounted(() => {
startTimer()
})
onUpdated(() => {
endTimer()
})
return {
startTimer,
endTimer
}
}
实际应用中的注意事项
状态管理的边界处理
import { ref, watch, computed } from 'vue'
export function useTaskManager() {
const tasks = ref([])
// 数据验证
const addTask = (task) => {
if (!task || !task.text.trim()) {
throw new Error('任务内容不能为空')
}
if (tasks.value.some(t => t.text === task.text.trim())) {
throw new Error('任务已存在')
}
tasks.value.push({
id: Date.now(),
text: task.text.trim(),
completed: false,
createdAt: new Date()
})
}
// 防抖处理
const debouncedSave = debounce(() => {
localStorage.setItem('tasks', JSON.stringify(tasks.value))
}, 500)
// 监听数据变化并保存
watch(tasks, () => {
debouncedSave()
}, { deep: true })
return {
tasks,
addTask
}
}
// 防抖函数实现
function debounce(func, wait) {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
错误处理和用户反馈
import { ref } from 'vue'
export function useErrorHandler() {
const error = ref(null)
const loading = ref(false)
const handleError = (error) => {
console.error('Error occurred:', error)
error.value = error.message || '未知错误'
// 3秒后自动清除错误
setTimeout(() => {
error.value = null
}, 3000)
}
const handleAsyncOperation = async (operation) => {
try {
loading.value = true
const result = await operation()
return result
} catch (err) {
handleError(err)
throw err
} finally {
loading.value = false
}
}
return {
error,
loading,
handleError,
handleAsyncOperation
}
}
总结与展望
Vue 3 的 Composition API 为前端开发带来了革命性的变化,它不仅提供了更灵活的组件组织方式,还大大增强了代码的可复用性和可维护性。通过本文的实践案例,我们可以看到:
- 模块化开发:组合函数让逻辑更加模块化,便于测试和复用
- 响应式编程:更直观地处理数据变化和状态管理
- 性能优化:合理使用计算属性和响应式系统提升应用性能
- 最佳实践:遵循清晰的编码规范和设计模式
随着 Vue 3 生态系统的不断完善,Composition API 将在未来的前端开发中发挥越来越重要的作用。开发者应该积极拥抱这一变化,通过实践来掌握其精髓,构建更加优雅、高效的 Vue 应用。
在未来的发展中,我们期待看到更多基于 Composition API 的优秀工具和库出现,进一步丰富 Vue 3 的开发体验。同时,随着 TypeScript 在 Vue 中的深入集成,组合函数的类型安全性也将得到显著提升,为大型项目的开发提供更好的支持。

评论 (0)