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