前言
经过前20天的学习,我们已经掌握了Vue 3的核心概念、组合式API、路由、状态管理等关键技术。今天将通过一个完整的项目实践------Todo应用 ,将所学知识融会贯通。我们将为Todo应用添加编辑、删除、过滤等进阶功能,并优化代码结构。
一、项目回顾与初始化
假设已通过Vue CLI创建了一个基础Todo应用,当前功能包括:
- 添加Todo项
- 展示Todo列表
- 切换Todo完成状态
项目结构如下:
src/
├── components/
│ └── TodoItem.vue
├── store/
│ └── index.js # Vuex状态管理
├── router/
│ └── index.js # 路由配置
├── views/
│ └── TodoList.vue
└── App.vue
二、功能实现:编辑Todo项
目标:双击Todo文本进入编辑模式,输入后保存修改。
1. 组件通信优化
在TodoItem.vue
中,添加编辑逻辑:
<template>
<div class="todo-item">
<!-- 双击触发编辑模式 -->
<span
v-if="!isEditing"
@dblclick="enterEditMode"
>{{ todo.text }}</span>
<!-- 编辑输入框 -->
<input
v-else
type="text"
v-model="editedText"
@blur="saveEdit"
@keyup.enter="saveEdit"
/>
</div>
</template>
<script setup>
import { ref } from 'vue';
const props = defineProps(['todo']);
const emit = defineEmits(['edit-todo']);
const isEditing = ref(false);
const editedText = ref(props.todo.text);
const enterEditMode = () => {
isEditing.value = true;
};
const saveEdit = () => {
if (editedText.value.trim()) {
emit('edit-todo', {
id: props.todo.id,
text: editedText.value.trim()
});
isEditing.value = false;
}
};
</script>
2. Vuex中实现编辑Mutation
在store/index.js
中添加:
mutations: {
EDIT_TODO(state, payload) {
const todo = state.todos.find(t => t.id === payload.id);
if (todo) todo.text = payload.text;
}
}
三、功能实现:删除Todo项(续)
1. 添加删除按钮
修改TodoItem.vue
模板:
<template>
<div class="todo-item">
<!-- ...原有内容... -->
<button
2. Vuex中实现删除Mutation
在store/index.js
中添加删除逻辑:
mutations: {
// ...其他mutation...
DELETE_TODO(state, todoId) {
state.todos = state.todos.filter(t => t.id !== todoId);
}
}
3. 组件中触发删除事件
在TodoItem.vue
中添加删除按钮逻辑:
<template>
<div class="todo-item">
<!-- ...原有内容... -->
<button @click="deleteTodo">🗑️</button>
</div>
</template>
<script setup>
const deleteTodo = () => {
emit('delete-todo', props.todo.id);
};
</script>
在父组件TodoList.vue
中处理事件:
<template>
<TodoItem
v-for="todo in filteredTodos"
:key="todo.id"
:todo="todo"
@edit-todo="editTodo"
@delete-todo="deleteTodo"
/>
</template>
<script setup>
const deleteTodo = (id) => {
store.commit('DELETE_TODO', id);
};
</script>
四、功能实现:过滤Todo项
目标:添加"全部/已完成/未完成"过滤功能。
1. Vuex中添加过滤状态
// store/index.js
state: {
todos: [],
filter: 'all' // all | completed | active
},
getters: {
filteredTodos: (state) => {
switch (state.filter) {
case 'completed':
return state.todos.filter(t => t.done);
case 'active':
return state.todos.filter(t => !t.done);
default:
return state.todos;
}
}
},
mutations: {
SET_FILTER(state, filter) {
state.filter = filter;
}
}
2. 添加过滤组件
创建components/FilterTodos.vue
:
<template>
<div class="filters">
<button
v-for="filter in filters"
:key="filter"
:class="{ active: currentFilter === filter }"
@click="setFilter(filter)"
>
{{ filter }}
</button>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const filters = ['all', 'active', 'completed'];
const currentFilter = computed(() => store.state.filter);
const setFilter = (filter) => {
store.commit('SET_FILTER', filter);
};
</script>
<style scoped>
.active {
background: #42b983;
color: white;
}
</style>
3. 在父组件中集成
<!-- TodoList.vue -->
<template>
<FilterTodos />
<!-- ...其他内容... -->
</template>
五、代码优化与重构
目标:提升代码可维护性
1. 模块化Vuex Store
创建store/modules/todos.js
:
export default {
state: () => ({
todos: [],
filter: 'all'
}),
mutations: { /* ... */ },
getters: { /* ... */ }
}
更新store/index.js
:
import todosModule from './modules/todos';
export default createStore({
modules: {
todos: todosModule
}
});
2. 组件拆分
将Todo列表项拆分为components/TodoList.vue
,逻辑与视图分离。
六、添加过渡动画
优化用户体验,为Todo项添加进场/退场动画:
<!-- TodoList.vue -->
<template>
<TransitionGroup name="todo-list" tag="ul">
<TodoItem
v-for="todo in filteredTodos"
:key="todo.id"
:todo="todo"
/>
</TransitionGroup>
</template>
<style>
.todo-list-enter-active,
.todo-list-leave-active {
transition: all 0.5s ease;
}
.todo-list-enter-from,
.todo-list-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>
七、完整功能演示
最终实现功能:
- ✅ 添加Todo(回车确认)
- ✅ 编辑Todo(双击修改)
- ✅ 删除Todo(点击垃圾桶图标)
- ✅ 切换完成状态(复选框)
- ✅ 过滤显示(全部/进行中/已完成)
- 🎨 平滑的过渡动画
八、总结与扩展
今日收获:
- 实践了组件通信的多种方式(props/emit/Vuex)
- 掌握了Vuex状态管理模式的核心流程
- 体验了组合式API的模块化优势
- 学会使用Transition组件实现动画
扩展挑战:
- 添加本地存储持久化(localStorage)
- 实现拖拽排序功能
- 增加分类标签系统
- 部署到Vercel/Netlify