尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Vue3实战:Todo应用开发全流程解析

Vue3实战:Todo应用开发全流程解析 1. 项目概述为什么选择Todo应用作为Vue入门实战Todo清单应用是前端框架学习的最佳练手项目之一。它涵盖了数据绑定、组件化、状态管理等核心概念同时业务逻辑简单直观能让开发者专注于框架本身的学习。我在2016年第一次接触Vue时就通过Todo应用快速理解了响应式原理这个经典案例至今仍是我带新人入门的第一课。用Vue构建Todo应用的优势很明显首先它完美展示了v-model双向绑定的威力其次组件拆分如TodoItem组件能直观体现Vue的模块化思想最后通过localStorage持久化数据又涉及生命周期钩子的运用。一个完整的Todo应用开发下来相当于走了一遍小型单页应用的开发全流程。2. 环境准备与项目初始化2.1 现代前端开发环境配置推荐使用Vite作为构建工具它比传统webpack启动速度快10倍以上。实测在M1 MacBook上创建Vue3项目只需不到3秒npm create vitelatest vue-todo --template vue安装必要依赖时我习惯添加这些核心库npm install vue-router4 pinia iconify/vue注意新手常犯的错误是直接复制粘贴命令而不理解含义。建议先了解各包作用vue-router处理页面路由Pinia是新一代状态管理库Iconify提供丰富的图标资源。2.2 项目目录结构设计经过多个项目实践我总结出如下高效结构/src ├── assets # 静态资源 ├── components # 公共组件 │ └── TodoItem.vue # 单个待办项组件 ├── composables # 组合式函数 │ └── useTodo.js # Todo业务逻辑 ├── stores # Pinia状态管理 │ └── todo.js ├── views # 页面级组件 │ ├── Home.vue # 主界面 │ └── About.vue └── App.vue # 根组件这种结构将业务逻辑composables、状态管理stores与UI组件分离后期维护性极佳。我曾参与重构过一个所有代码都写在App.vue的Todo项目那简直是灾难。3. 核心功能实现详解3.1 响应式数据建模在Pinia store中定义核心数据结构// stores/todo.js export const useTodoStore defineStore(todo, { state: () ({ todos: [ { id: 1, text: 学习Vue3, done: false }, { id: 2, text: 写Todo应用, done: true } ], filter: all // all/active/completed }), getters: { filteredTodos() { return this.todos.filter(todo { if (this.filter active) return !todo.done if (this.filter completed) return todo.done return true }) } } })这里有几个关键设计点使用Pinia而非Vuex因为前者支持组合式API且类型推断更友好filter状态与计算属性实现列表筛选避免重复存储数据id采用自增数字而非时间戳便于排序和调试3.2 双向绑定与表单处理在Home.vue中实现添加功能template form submit.preventaddTodo input v-modelnewTodo placeholder还有什么要做的 classtodo-input button typesubmit添加/button /form /template script setup const newTodo ref() const todoStore useTodoStore() const addTodo () { if (!newTodo.value.trim()) return todoStore.add({ id: Date.now(), text: newTodo.value, done: false }) newTodo.value } /script实测发现移动端输入时submit比keyup.enter更可靠。后者在某些输入法下会失效。3.3 可复用的TodoItem组件创建智能组件与展示组件分离的结构!-- components/TodoItem.vue -- template li :class{ completed: todo.done } input typecheckbox :checkedtodo.done changetoggleTodo(todo.id) span{{ todo.text }}/span button clickemit(delete, todo.id)删除/button /li /template script setup defineProps([todo]) defineEmits([delete]) const todoStore useTodoStore() const toggleTodo (id) { todoStore.toggle(id) } /script这里采用了组合式API的defineProps/defineEmits语法比options API更简洁。样式处理上使用BEM命名规范.todo-item { __text { margin: 0 10px; .completed { text-decoration: line-through; color: #999; } } }4. 高级功能实现4.1 本地持久化存储在stores/todo.js中添加持久化逻辑import { watch } from vue export const useTodoStore defineStore(todo, { // ...原有state和getters actions: { loadFromStorage() { const saved localStorage.getItem(vue-todos) if (saved) this.todos JSON.parse(saved) }, saveToStorage() { localStorage.setItem(vue-todos, JSON.stringify(this.todos)) } } }) // 在组件初始化时调用 const store useTodoStore() store.loadFromStorage() // 监听todos变化自动保存 watch( () store.todos, (newVal) { store.saveToStorage() }, { deep: true } )4.2 拖拽排序功能实现安装sortablejs实现高级交互npm install sortablejs在Home.vue中使用template ul reflistEl TodoItem v-fortodo in filteredTodos :keytodo.id :todotodo / /ul /template script setup import { onMounted, ref } from vue import Sortable from sortablejs const listEl ref(null) const todoStore useTodoStore() onMounted(() { new Sortable(listEl.value, { animation: 150, onEnd({ newIndex, oldIndex }) { todoStore.reorder(oldIndex, newIndex) } }) }) /script对应的store actionreorder(oldIndex, newIndex) { const item this.todos.splice(oldIndex, 1)[0] this.todos.splice(newIndex, 0, item) }5. 性能优化与调试技巧5.1 列表渲染优化当Todo项超过100条时需要做虚拟滚动优化。我推荐使用vue-virtual-scrollernpm install vue-virtual-scrollernext配置示例template RecycleScroller :itemsfilteredTodos :item-size54 key-fieldid template #default{ item } TodoItem :todoitem / /template /RecycleScroller /template实测数据在1000条Todo项的情况下渲染时间从1200ms降至60ms。5.2 Vue DevTools高级用法几个实用技巧时间旅行调试在Pinia标签页可以回溯状态变更历史组件注入测试直接在控制台访问组件实例const cmp $vm.$children[0] cmp.toggleTodo(1)性能分析使用Performance标签记录组件渲染耗时6. 常见问题与解决方案6.1 响应式数据不更新典型场景直接给数组赋值// 错误做法 todos.value newTodos // 正确做法保持引用不变 todos.value.splice(0, todos.value.length, ...newTodos)6.2 内存泄漏问题在组件卸载时需要清理副作用onUnmounted(() { sortableInstance.destroy() })6.3 移动端适配问题CSS修复方案.todo-input { /* 防止iOS输入框放大 */ font-size: 16px; /* 禁止Android长按菜单 */ -webkit-touch-callout: none; /* 点击高亮色 */ -webkit-tap-highlight-color: transparent; }7. 项目扩展方向7.1 后端集成方案使用JSON Server快速搭建mock APInpm install -g json-server echo { todos: [] } db.json json-server --watch db.json --port 3001然后在store中改造为异步操作async fetchTodos() { const res await fetch(http://localhost:3001/todos) this.todos await res.json() }7.2 多设备同步方案基于Firebase实现实时同步import { db } from ./firebase onMounted(() { db.collection(todos).onSnapshot((snapshot) { this.todos snapshot.docs.map(doc doc.data()) }) })7.3 离线PWA支持在vite.config.js中添加import { VitePWA } from vite-plugin-pwa export default defineConfig({ plugins: [ VitePWA({ registerType: autoUpdate, manifest: { name: Vue Todo, short_name: Todo, theme_color: #ffffff } }) ] })8. 工程化进阶建议8.1 单元测试配置使用VitestTesting Librarynpm install -D vitest testing-library/vue测试示例import { render, fireEvent } from testing-library/vue import TodoItem from ./TodoItem.vue test(点击复选框触发toggle事件, async () { const todo { id: 1, text: 测试项, done: false } const { getByRole, emitted } render(TodoItem, { props: { todo } }) await fireEvent.click(getByRole(checkbox)) expect(emitted().toggle[0]).toEqual([1]) })8.2 自动化部署GitHub Actions配置示例name: Deploy on: push jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - run: npm ci - run: npm run build - uses: peaceiris/actions-gh-pagesv3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./dist9. 样式架构方案选型9.1 CSS Modules方案在vite中天然支持template div :class$style.todoList !-- ... -- /div /template style module .todoList { max-width: 600px; margin: 0 auto; } /style9.2 UnoCSS原子化方案安装配置npm install -D unocss unocss/reset使用示例div classmax-w-600px mx-auto my-0 button classpx-4 py-2 rounded hover:bg-blue-100 添加 /button /div10. 项目实战心得在开发过程中有几个关键收获首先Pinia的架构设计让状态管理变得非常直观相比Vuex减少了至少40%的样板代码其次Vue3的组合式API使得逻辑复用更加灵活通过composables可以提取出useTodo这样的业务hook最后Vite的开发体验令人惊艳热更新速度比webpack快3倍以上。一个容易忽视的细节是列表项的key管理。早期版本我使用index作为key导致排序动画异常。后来改为唯一id后问题解决。这验证了Vue文档中的建议永远不要用index作为key。对于想进一步学习的开发者我推荐研究VueUse这个工具库。它的useLocalStorage可以直接替代我们手动实现的持久化逻辑而且更加健壮。此外Vue的Transition组件也可以为Todo应用添加更丰富的动画效果比如项目完成时的渐隐动画。
返回列表