示例:用 Composition API 重构一个组件
通过前几节的学习,你已经了解了 Composition API 的优势和基本用法。本节将通过一个实际示例,展示如何将一个使用 Options API 编写的组件重构为 Composition API 版本。你将看到逻辑如何从分散变为集中,并体会这种方式在可读性、复用性和维护性上的提升。
原始组件:Options API 版本
假设我们有一个任务管理组件,包含任务列表、筛选功能和计数器功能,使用 Options API 实现:
<!-- TaskManager.vue -->
<template>
<div>
<h1>任务管理</h1>
<input v-model="newTask" placeholder="添加新任务" @keyup.enter="addTask" />
<select v-model="filter">
<option value="all">全部</option>
<option value="completed">已完成</option>
<option value="pending">未完成</option>
</select>
<ul>
<li v-for="task in filteredTasks" :key="task.id" :class="{ completed: task.done }">
<input type="checkbox" v-model="task.done" />
{{ task.title }}
</li>
</ul>
<p>任务总数:{{ taskCount }}</p>
<p>点击次数:{{ clickCount }}</p>
<button @click="incrementClick">点击我</button>
</div>
</template>
<script>
export default {
data() {
return {
newTask: '',
filter: 'all',
tasks: [
{ id: 1, title: '学习 Vue 3', done: false },
{ id: 2, title: '完成项目', done: true }
],
clickCount: 0
};
},
computed: {
filteredTasks() {
if (this.filter === 'completed') {
return this.tasks.filter(task => task.done);
} else if (this.filter === 'pending') {
return this.tasks.filter(task => !task.done);
}
return this.tasks;
},
taskCount() {
return this.tasks.length;
}
},
methods: {
addTask() {
if (this.newTask.trim()) {
this.tasks.push({
id: Date.now(),
title: this.newTask,
done: false
});
this.newTask = '';
}
},
incrementClick() {
this.clickCount++;
}
}
};
</script>
<style scoped>
.completed {
text-decoration: line-through;
color: gray;
}
</style>
分析问题
- 逻辑分散:任务相关逻辑(
tasks、filteredTasks、addTask)和计数器逻辑(clickCount、incrementClick)分布在data、computed和methods中。 - 复用性低:若需在其他组件复用计数器逻辑,只能通过 Mixin,增加了复杂性。
- 可维护性:新增功能时,需在多个选项间跳转修改。
重构为 Composition API
我们将上述组件重构为 Composition API,将逻辑按功能模块组织,并提取可复用的部分。
重构后的代码
<!-- TaskManager.vue -->
<template>
<div>
<h1>任务管理</h1>
<input v-model="taskState.newTask" placeholder="添加新任务" @keyup.enter="addTask" />
<select v-model="filter">
<option value="all">全部</option>
<option value="completed">已完成</option>
<option value="pending">未完成</option>
</select>
<ul>
<li v-for="task in filteredTasks" :key="task.id" :class="{ completed: task.done }">
<input type="checkbox" v-model="task.done" />
{{ task.title }}
</li>
</ul>
<p>任务总数:{{ taskCount }}</p>
<p>点击次数:{{ clickCount }}</p>
<button @click="incrementClick">点击我</button>
</div>
</template>
<script>
import { reactive, ref, computed } from 'vue';
// 提取计数器逻辑
function useCounter() {
const clickCount = ref(0);
const incrementClick = () => {
clickCount.value++;
};
return { clickCount, incrementClick };
}
// 任务管理逻辑
function useTasks() {
const taskState = reactive({
newTask: '',
tasks: [
{ id: 1, title: '学习 Vue 3', done: false },
{ id: 2, title: '完成项目', done: true }
]
});
const filter = ref('all');
const filteredTasks = computed(() => {
if (filter.value === 'completed') {
return taskState.tasks.filter(task => task.done);
} else if (filter.value === 'pending') {
return taskState.tasks.filter(task => !task.done);
}
return taskState.tasks;
});
const taskCount = computed(() => taskState.tasks.length);
const addTask = () => {
if (taskState.newTask.trim()) {
taskState.tasks.push({
id: Date.now(),
title: taskState.newTask,
done: false
});
taskState.newTask = '';
}
};
return { taskState, filter, filteredTasks, taskCount, addTask };
}
export default {
setup() {
const { taskState, filter, filteredTasks, taskCount, addTask } = useTasks();
const { clickCount, incrementClick } = useCounter();
return {
taskState,
filter,
filteredTasks,
taskCount,
addTask,
clickCount,
incrementClick
};
}
};
</script>
<style scoped>
.completed {
text-decoration: line-through;
color: gray;
}
</style>
重构亮点
- 逻辑集中:
- 任务相关逻辑(
taskState、filteredTasks、addTask)集中在useTasks函数中。 - 计数器逻辑(
clickCount、incrementClick)独立为useCounter。
- 任务相关逻辑(
- 可复用性:
useCounter可在其他组件中直接导入复用,无命名冲突。
<script> import { useCounter } from './composables/counter'; export default { setup() { return useCounter(); } }; </script> - 清晰性:
setup只负责组合和返回逻辑,职责单一。- 每个函数专注于单一功能,便于调试和扩展。
重构后的改进
1. 可读性提升
- Options API:开发者需在
data、computed和methods间跳转,理解任务和计数器的关系。 - Composition API:相关代码集中在一个函数内,一目了然。
2. 扩展性增强
添加新功能(如删除任务)只需修改 useTasks:
function useTasks() {
// ... 现有代码 ...
const removeTask = (taskId) => {
taskState.tasks = taskState.tasks.filter(task => task.id !== taskId);
};
return { taskState, filter, filteredTasks, taskCount, addTask, removeTask };
}
- 对比:Options API 需在
methods中新增方法,分散性增加。
3. 类型支持
Composition API 天然支持 TypeScript:
interface Task {
id: number;
title: string;
done: boolean;
}
function useTasks() {
const taskState = reactive({
newTask: '',
tasks: [] as Task[]
});
// ... 其余逻辑 ...
}
注意事项
- 响应性保持:
- 返回
taskState.tasks而非解构{ tasks },避免丢失响应性。
- 返回
- 函数命名:
- 使用
useXxx前缀,遵循社区惯例,便于识别可复用逻辑。
- 使用
- 模块化:
- 可将
useTasks和useCounter提取到单独文件(如composables/tasks.js),进一步分离关注点。
- 可将
总结
通过将任务管理组件从 Options API 重构为 Composition API,我们实现了逻辑的集中化和复用性提升。Composition API 的函数式风格让代码更模块化、可测试,尤其适合复杂组件开发。掌握这种重构技巧后,你可以轻松应对更大规模的项目。下一章将深入高级 Composition API 特性,继续探索其强大功能!
