《React 状态管理大作战:一个 Todo App 的“家庭伦理剧”》

6 阅读5分钟

《React 状态管理大作战:一个 Todo App 的“家庭伦理剧”》

在前端开发的江湖里,流传着一句老话:

“写个 Todo App,胜过千行 Hello World。”

今天,我们就来围观一场发生在 React 家族内部的“家庭伦理剧”——
父组件(App)如何用爱(状态)和规矩(props)管理三个性格迥异的孩子:TodoInput、TodoList 和 TodoStats。

别担心,这不是枯燥的技术文档,而是一出有笑点、有冲突、有和解的“组件家庭肥皂剧”。准备好了吗?幕布拉开!


🎭 第一幕:家有三宝,各司其职

在这个 React 小家庭里,有三位主角:

  • TodoInput:活泼好动的小儿子,负责“收任务”。

    “妈!有人要加新任务啦!”

  • TodoList:认真负责的大女儿,负责“列任务、勾任务、删任务”。

    “爸,这条任务已完成,请打钩!”

  • TodoStats:冷静理性的二哥,负责“报数据”。

    “全家注意:当前待办 3 条,已完成 2 条,还有 1 条在摸鱼!”

而他们的父母——App 组件——则稳坐中军帐,手握全家唯一的“账本”:todos 数组。

💡 在 React 的世界里,数据只能从父母流向孩子(单向数据流) ,孩子不能擅自改账本,只能“打报告”请求修改。


📜 第二幕:家规第一条——“所有数据归爸妈管!”

const [todos, setTodos] = useState([...]);

这行代码,就是这个家庭的“宪法”。

  • todos 是全家共享的唯一真相源(Single Source of Truth)
  • 三个孩子想看任务?行!爸妈通过 props 发下去。
  • 想改任务?不行!必须调用爸妈提供的“专用热线”(比如 onAddonDelete)。

❌ 错误示范(孩子造反):

// TodoList 内部(错误!)
todo.completed = true; // ❌ 直接改 props?门都没有!

React 会冷冷地说:“Props 是只读的,你没有权限。

✅ 正确操作(打报告):

// TodoList 调用父母给的函数
<button onClick={() => onDelete(todo.id)}>删除</button>

这就像孩子对爸妈说:“我想删掉‘洗碗’这条任务……求批准!”
爸妈审核后,用 setTodos 更新账本,全家自动同步。


🧩 第三幕:父子通信——“传话筒”与“回调函数”

👨‍👦 场景 1:小儿子(TodoInput)要加任务

// App 父组件
<TodoInput onAdd={addTodo} />

// TodoInput 子组件
const { onAdd } = props;
// ...
onAdd(inputValue); // 打电话给爸妈:“我要加任务!”
  • onAdd 就是爸妈给小儿子的“专线电话”。
  • 小儿子一按回车,就拨通电话,把任务内容上报。
  • 爸妈收到后,更新 todos,全家重新渲染。

📞 这就是“子 → 父通信”:通过 props 传递函数,子组件调用它来“上报事件”。


👧‍👦 场景 2:大女儿(TodoList)要删任务或打钩

// App
<TodoList 
  todos={todos}
  onDelete={deleteTodo}
  onToggle={toggleTodo}
/>

// TodoList
<input 
  onChange={() => onToggle(todo.id)} 
/>
<button onClick={() => onDelete(todo.id)}>删除</button>
  • 大女儿不直接操作 todos,而是调用 onToggle 或 onDelete
  • 爸妈收到指令,用 filter 或 map 生成新数组,替换旧账本。

React 不允许直接修改状态(mutate) ,必须“生成新副本”。
就像不能在原账本上涂改,必须誊抄一本新的!


📊 第四幕:二哥(TodoStats)的“数据播报”

const activeCount = todos.filter(todo => !todo.completed).length;
const completedCount = todos.filter(todo => todo.completed).length;

<TodoStats 
  total={todos.length}
  active={activeCount}
  completed={completedCount}
  onClearCompleted={clearCompleted}
/>
  • 二哥自己不存数据,全靠爸妈实时“喂”。

  • 他还会根据数据决定是否显示“清除已完成”按钮:

    {completed > 0 && <button>清除已完成任务</button>}
    
  • 点击按钮?照样打报告:onClearCompleted()

📢 兄弟组件之间从不直接对话!
TodoInput 想知道总任务数?不行!必须问爸妈。
TodoStats 想通知 TodoList 刷新?不行!必须通过爸妈中转。

这就是 “兄弟组件通信 = 父组件中转 + 响应式更新”


💾 第五幕:持久化——“断电也不丢任务!”

useEffect(() => {
  localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
  • 爸妈有个“保险柜”(localStorage),每次账本一变,立刻存档。

  • 下次打开页面,先从保险柜取回历史数据:

    const [todos, setTodos] = useState(() => {
      const saved = localStorage.getItem('todos');
      return saved ? JSON.parse(saved) : [];
    });
    

🔒 数据持久化 = useEffect + localStorage,简单又可靠!


🎨 第六幕:UI 细节——“完成的任务要划掉!”

<li className={todo.completed ? 'completed' : ''}>
  • CSS 配合:

    .completed span {
      text-decoration: line-through;
      color: #999;
    }
    
  • 勾选框状态由 checked={todo.completed} 控制。

  • 用户点击?触发 onToggle,爸妈更新状态,UI 自动同步。

React 的核心哲学:UI = f(state)
状态一变,界面自动变,无需手动操作 DOM!


⚠️ 第七幕:常见“家庭矛盾”与解决方案

问题原因解决方案
点添加没反应忘了 e.preventDefault()表单提交必须阻止默认刷新!
列表不更新直接修改了数组(如 push用 [...todos, newTodo] 创建新数组
删除失效key 用了 index用唯一 id 作 key
数据丢失没做持久化加 useEffect + localStorage

🏁 终章:React 状态管理的“家庭美德”

这个 Todo App 虽小,却完美体现了 React 的核心思想:

  1. 单向数据流:数据从父到子,清晰可预测。
  2. 状态提升:共享状态放在最近的共同父组件。
  3. 不可变性:永远用新状态替换旧状态,不直接修改。
  4. 组件职责分离:输入、列表、统计各干各的,互不干扰。
  5. 响应式更新:状态一变,相关 UI 自动刷新。

💡 真正的工程化,不是炫技,而是让代码像家庭一样——各司其职,沟通有序,和谐运转。


🎁 彩蛋:为什么不用 V-model?

你可能听说 Vue 有 v-model 双向绑定,很方便。
但 React 说:“双向绑定像自由恋爱,容易乱;单向数据流像包办婚姻,虽然老派,但秩序井然!

  • 单向流让数据流动可追踪、可调试、可预测
  • 虽然多写几行代码,但换来的是大型应用的可维护性

就像这个家庭:
孩子不能擅自改家规,一切听爸妈安排——
虽然有点“专制”,但家里从不乱套!


下次当你写 React 组件时,不妨想想:

“我的数据,是不是也该有个‘负责任的父母’来统一管理?”

Happy Coding!愿你的状态永不混乱,你的组件永远和睦!😊