Vue 3 30天精进之旅:Day 21 - 项目实践:打造功能完备的Todo应用

前言

经过前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(点击垃圾桶图标)
  • ✅ 切换完成状态(复选框)
  • ✅ 过滤显示(全部/进行中/已完成)
  • 🎨 平滑的过渡动画

八、总结与扩展

今日收获

  1. 实践了组件通信的多种方式(props/emit/Vuex)
  2. 掌握了Vuex状态管理模式的核心流程
  3. 体验了组合式API的模块化优势
  4. 学会使用Transition组件实现动画

扩展挑战

  • 添加本地存储持久化(localStorage)
  • 实现拖拽排序功能
  • 增加分类标签系统
  • 部署到Vercel/Netlify
相关推荐
禅思院26 分钟前
路由性能高可用架构实战方案
前端·架构·前端框架
IT_陈寒43 分钟前
React状态更新总是不及时?你可能漏了这步批处理机制
前端·人工智能·后端
恋猫de小郭1 小时前
AI Agent 开发究竟是啥?如何用 AI 开发 Agent ?深入浅出给你一套概念
android·前端·ai编程
前端双越老师1 小时前
我开发 AI Agent 项目踩过的 5个坑
前端·agent·全栈
晓得迷路了1 小时前
栗子前端技术周刊第 134 期 - React Router v8、TypeScript 7 RC、React Native 0.86...
前端·javascript·react.js
Carson带你学Android1 小时前
Android 17 正式发布:AI 终于成了系统能力
android·前端·ai编程
Mike_jia1 小时前
ZbxTable:Zabbix开源报表神器,从运维数据到决策洞察的最后一公里
前端
LinXunFeng11 小时前
Obsidian - 使用 Share Note 分享笔记并自部署
前端·笔记·github
乘风gg14 小时前
为什么AI 时代来临,大部分人吃不到红利
前端·ai编程·claude
恋猫de小郭15 小时前
Android 限制侧载新进展,谷歌联合国内厂商推验证计划
android·前端·flutter