从 Todo 项目看 React 组件通信:核心逻辑与优化技巧

React 真正让人头疼的,从来不是 Hooks 的 API,而是:组件一多,数据到底该往哪放?

很多人写 React,都是典型的「能跑就行」👇

  • Props 层层传递(Prop Drilling),改一个逻辑要动五个文件
  • 兄弟组件状态不同步,Bug 像"量子纠缠"
  • Debug 像破案,根本不知道是谁把 todos 改了

问题不在语法,在"通信模型"没设计清楚。

今天,我们就通过一个经典的 Todo List 项目 ,把 React 组件通信的底层逻辑一次性讲透


一、架构先行:先站在"上帝视角"看组件树

在 React 里,通信问题 80% 不是代码问题,而是结构问题。

1️⃣ 组件分工一览

arduino 复制代码
App
├── TodoInput   // 动作发起者
├── TodoList    // 状态展示者
└── TodoStats   // 数据统计者
  • App(数据中心)
    唯一的 Source of Truth ,持有 todos
  • TodoInput(动作发起者)
    只负责收集输入,不关心数据存哪
  • TodoList(展示者)
    渲染列表,并触发"删除 / 切换完成态"
  • TodoStats(统计者)
    根据数据计算统计信息,并提供"清空"能力

2️⃣ 核心原则:状态提升(Lifting State Up)

只要多个组件共享同一份数据,这份数据就必须上移。

为什么 todos 不能放在 TodoList

因为:

  • TodoInput 要新增
  • TodoStats 要统计
  • TodoList 要展示

👉 三者共享 ⇒ 数据只能放在最近的共同父组件 App

这不是最佳实践,这是 React 的设计前提


二、三大通信场景深度拆解


1️⃣ 父 → 子:Props 不是"传数据",而是"授予使用权"

App.jsx 中:

ini 复制代码
<TodoList
  todos={todos}
  onDelete={deleteTodo}
  onToggle={toggleTodo}
/>

💡 深度理解 Props:

Props 不是拷贝一份数据

而是父组件给子组件的「只读使用权」

📌 类比一下:

  • 父组件是老板
  • Props 是任务清单
  • 子组件只能照单执行,不能私自改单

子组件永远不应该修改 Props。


2️⃣ 子 → 父:从"改数据"到"上报事件"

这是 React 新手最容易犯错的地方。

❌ 错误思维:

"我能不能在子组件里直接 props.todos.push()?"

✅ 正确模型:

子组件只负责"上报行为",不负责"决定结果"


TodoInput 中:

ini 复制代码
const TodoInput = ({ onAdd }) => {
  const [text, setText] = useState('');

  const handleSubmit = () => {
    if (!text.trim()) return;
    onAdd(text); // 📢 向父组件上报:新增了一个任务
    setText('');
  };
};

❓ 为什么一定要用函数?

因为:

  • 只有 App 持有 setTodos
  • 只有 App 有资格决定数据如何变化
  • Bug 出现时,你只需要检查 一个地方

把"修改权"集中,是 React 可维护性的核心来源。


3️⃣ 兄弟组件通信:最熟悉的陌生人

TodoStats 如何"知道" TodoList 里发生了变化?

答案是:它并不知道。


正确流程是这样的:

1️⃣ TodoList 触发 onToggle(子 → 父)

2️⃣ App 更新 todos

3️⃣ App 重新渲染

4️⃣ 新的 todos 通过 Props 下发给 TodoStats

💡 金句记住这一句:

在 React 中,兄弟组件之间永远"互不认识",它们只对父组件负责。


三、高阶技巧:避开通信中的"隐形坑"


1️⃣ 别把 Props 当 State:警惕派生状态

❌ 常见错误:

scss 复制代码
const [activeCount, setActiveCount] = useState(0);

useEffect(() => {
  setActiveCount(todos.filter(t => !t.completed).length);
}, [todos]);

✅ 正确做法:

ini 复制代码
const activeCount = todos.filter(todo => !todo.completed).length;

📌 原则只有一句话:

如果一个值可以通过 Props 计算得出,就永远不要用 useState 存。

这类数据叫:派生状态(Derived State)


2️⃣ 只有"副作用"才进 useEffect

在 Todo 项目中,真正需要 useEffect 的只有一件事👇

javascript 复制代码
useEffect(() => {
  localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);

✅ 特点:

  • 不影响渲染
  • 依赖明确
  • 职责单一

👉 这才是 useEffect 的正确姿势。


四、总结:React 通信的"秩序感"

React 坚持单向数据流,不是为了限制你,而是为了建立秩序

📌 记住这 4 句话:

  • 数据向上找:共享就提升
  • 展示向下走:子组件只看 Props
  • 修改调函数:子组件动口不动手
  • 兄弟不直连:父组件来转达

🔥 最后送你 5 句开发口诀

✅ 状态放对位,代码省一半

✅ Props 是合同,子组件得照办

✅ 改数据发请求,别在子组件里乱走

✅ 兄弟不传话,老爸来转达

✅ 算出来的状态,别用 State 存


🎯 结语

Todo 项目很小,

但它几乎覆盖了 React 组件通信的所有核心场景

如果你真正吃透这一套模型:

  • Redux / Zustand 会立刻"通电"
  • 项目越大,你越稳
  • 面试官问组件通信,你能讲设计而不是 API
相关推荐
LaughingZhu13 小时前
智能体经典范式构建:ReAct、Plan-and-Solve 与 Reflection
前端·react.js·前端框架
飞天狗14 小时前
长列表滚动卡顿怎么破:虚拟列表选型 + react-virtuoso 实战调优指南
react.js·性能优化
binbin_5217 小时前
React 井字棋教程:用一个小游戏理解组件和状态
前端·react.js·前端框架
张元清18 小时前
React useObjectUrl Hook:预览文件与 Blob,不留内存泄漏(2026)
javascript·react.js
真的想不出名儿21 小时前
Md编辑器整合-react
前端·react.js·编辑器
牛仔只喝牛奶2 天前
多端跨端技术选型:小程序 + H5 + App 的两栈拆分落地
react native·react.js
wear工程师2 天前
setState 到底是不是异步的?这题从 React 16 问到 18,标准答案早就变了
react.js·面试
wanghowie2 天前
LangGraph4j 落地(二)— ReAct 多步工具与 trace 扩展
前端·react.js·前端框架
csj502 天前
前端基础之《React(11)—state声明式》
前端·react.js
张元清2 天前
React useLocalStorage Hook:SSR 安全的持久化状态(2026)
javascript·react.js