从 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
相关推荐
San30.6 小时前
深度解析 React 组件化开发:从 Props 通信到样式管理的进阶指南
前端·javascript·react.js
Swift社区6 小时前
跨端路由设计:如何统一 RN 与 Web 的页面模型
前端·react.js·web3
王金涛7 小时前
React 缺失的"M"层:我开发了 Zenith,把完整的 Model 带回了前端
react.js
神秘的猪头7 小时前
彻底搞懂 React 组件通信:从 TodoList 实战出发,解锁 React 开发的“核心姿势” 🚀
前端·react.js·架构
鱼鱼块7 小时前
React 组件通信实战:从 props 入门到父子协作闭环
前端·react.js·面试
IT古董9 小时前
企业级官网全栈(React·Next.js·Tailwind·Axios·Headless UI·RHF·i18n)实战教程-前言
javascript·react.js·ui
Jinuss9 小时前
飞冰ice.js中Model数据初始化原理
前端·javascript·react.js