"写 Todo 是程序员的成人礼。"
如果你刚刚入坑 React,或者想巩固组件通信、状态提升、本地存储等核心概念,那么恭喜你!这篇文章将带你手把手打造一个功能完整、结构清晰、代码优雅的 React Todo 应用,并深入浅出地解释背后的原理。
更重要的是------我们不用 Redux、不用 Context、不用任何花里胡哨的库,只用 React 原生 Hooks + 父子通信,就能写出可维护、可扩展的代码!
🧠 为什么 Todo 应用值得认真对待?
别小看这个"加任务、删任务、标记完成"的小玩意儿。它完美涵盖了现代前端开发的三大核心问题:
- 状态管理(谁持有数据?谁修改数据?)
- 组件通信(父子怎么传?兄弟怎么聊?)
- 副作用处理(比如自动保存到 localStorage)
而 React 的哲学是:状态提升 + 单向数据流 。听起来高大上?其实很简单------让父组件当"管家",子组件只负责"汇报"和"展示" 。
🏗️ 项目结构预览
我们的应用由三个子组件构成:
TodoInput:输入新任务TodoList:展示并操作任务列表TodoStats:显示统计信息 & 清除已完成
它们都共享同一个状态:todos[]。这个数组由父组件 App 统一管理 ,并通过 props 传递给子组件。
✨ 这就是"状态提升"(Lifting State Up)的经典实践!
🔌 父子通信:React 的"单向数据流"哲学
👨👧 父 → 子:通过 props 传递数据
ini
<TodoList
todos={todos}
onDelete={deleteTodo}
onToggle={toggleTodo}
/>
父组件把 todos 数组和几个修改函数作为 props 传给子组件。子组件只能读,不能改------就像孩子只能看菜单,不能自己进厨房炒菜。
👧→👨 子 → 父:通过回调函数"打报告"
子组件想修改数据?必须调用父组件传来的函数:
scss
// 在 TodoInput 中
onAdd(inputValue); // 相当于:"爸,我想加个任务!"
// 在 TodoList 中
onToggle(todo.id); // "爸,这个任务我搞定了!"
这种模式确保了数据流向清晰、可预测,避免了"状态混乱"的噩梦。
💡 小贴士:React 不支持 Vue 那样的
v-model双向绑定,因为它认为"显式优于隐式"。虽然多写两行代码,但逻辑更透明!
🧩 兄弟组件如何"隔空对话"?
TodoInput 和 TodoList 是兄弟,它们之间没有直接通信 !所有交互都通过共同的父组件 App 中转:
TodoInput调用onAdd→ 父组件更新todos- 父组件把新
todos传给TodoList→ 列表自动刷新
这就是所谓的 "间接通信" ------看似绕路,实则解耦。兄弟组件互不依赖,未来拆分或替换都超轻松!
💾 自动保存到 localStorage:useEffect 的妙用
用户辛辛苦苦加了一堆任务,结果一刷新全没了?那可不行!
我们用 useEffect 监听 todos 变化,自动存到本地:
javascript
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
同时,初始化时从 localStorage 读取:
ini
const [todos, setTodos] = useState(() => {
const saved = localStorage.getItem('todos');
return saved ? JSON.parse(saved) : [];
});
🎉 用户体验瞬间拉满:关掉浏览器再打开,任务还在!妈妈再也不用担心我丢三落四了~
🎨 样式方案:Stylus + Vite,简洁又高效
我们用 Stylus 写样式(缩进语法,少写大括号),配合 Vite 极速构建。.styl 文件清爽易读:
yaml
.todo-app
max-width: 600px
margin: 0 auto
padding: 20px
.completed
text-decoration: line-through
color: #888
Vite 的 HMR(热更新)快如闪电,改一行样式,浏览器秒级响应------开发幸福感爆棚!
🧪 完整代码亮点回顾
- ✅ 状态集中管理 :所有
todos操作在App中定义 - ✅ 函数式更新 :
setTodos([...todos, newTodo])避免闭包陷阱 - ✅ 条件渲染 :
completed > 0 && <button>避免无效按钮 - ✅ 语义化 JSX :
<label>包裹 checkbox,提升可访问性 - ✅ 性能友好:无多余状态,无复杂计算
🤔 思考:为什么不用 Context 或 Zustand?
对于小型应用(如 Todo),过度设计反而增加复杂度 。Context 适合跨多层组件共享状态,Zustand 适合大型状态树。而我们的场景------三个组件 + 一个状态数组,用 props 足矣!
🚀 记住:简单即强大。能用 props 解决的问题,就别急着上状态管理库!
🎁 结语:你的第一个 React 应用,也可以很优雅
通过这个 Todo 应用,你不仅学会了组件通信,更理解了 React 的核心思想:状态驱动视图、单向数据流、组合优于继承。
下次面试官问:"React 组件怎么通信?" 你可以微微一笑,掏出这个项目说:
"看,我的 Todo,麻雀虽小,五脏俱全。"