news 2026/1/25 13:54:29

彻底搞懂 React 组件通信:从 TodoList 实战出发,解锁 React 开发的“核心姿势” [特殊字符]

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
彻底搞懂 React 组件通信:从 TodoList 实战出发,解锁 React 开发的“核心姿势” [特殊字符]

嘿,各位正在 React 门前反复横跳的新手小伙伴们!👋
是不是经常被“数据该放哪”、“怎么传给子组件”、“子组件想改父组件数据怎么办”这三个终极哲学问题搞得头大?别担心,今天咱们不聊虚的,直接通过一个经典的React + Stylus + Vite实战项目——Todos,带你一次性打通 React 组件通信的任督二脉!
不仅有代码,还有深度解析。准备好咖啡,我们要开始“套娃”了!


一、 项目背景:为什么我们要“套娃”?
在 Vue 里,你可能习惯了v-model的便捷,但在 React 的世界里,一切都是单向数据流。数据就像顺流而下的河水,从父组件流向子组件。
我们的项目结构如下:

  • App.js(大管家):持有所有数据(todos),负责逻辑处理。
  • TodoInput(输入框):负责产生新任务。
  • TodoList(展示列表):展示任务,并允许用户勾选完成或删除。
  • TodoStats(统计看板):展示剩余任务,提供一键清理。

二、 环境准备:Stylus 与 Vite 的碰撞
首先,我们使用的是 Vite 环境。在 React 中引入 CSS 预处理器(如 Stylus)非常简单。
1. 如何引入 Stylus
在 Vite 中,你只需要安装stylus

npm init stylus

然后像这样在App.jsx中引入即可:
JavaScript

import './styles/app.styl' // 直接引入,Vite 会自动帮你处理编译

为什么用 Stylus?因为它简洁,没有大括号和分号的束缚,和 React 的组件化思维很搭。


三、 核心灵魂:App 组件(数据中心化)
在 React 中,如果多个组件(比如输入框和列表)需要共享同一份数据,最正宗的做法就是状态提升(Lifting State Up)。我们将todos放在它们的共同父组件App中。
1. useState 的高级用法:惰性初始化
看这行代码:
JavaScript

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

💡 超级关键点: useState 可以接收一个函数作为参数。这叫“惰性初始化”。
为什么要这么做? 如果直接写 localStorage.getItem,每次组件重新渲染(render)时都会执行一遍 IO 读取。传一个函数,React 只会在组件第一次挂载时执行它。性能优化,从细节做起!

2. useEffect 的副作用管理
我们要实现“持久化存储”,即刷新页面数据不丢。
JavaScript

useEffect(() => { localStorage.setItem('todos', JSON.stringify(todos)); }, [todos]); // 只有当 todos 发生变化时,才会触发保存

这里使用了useEffect。它的第二个参数[todos]是依赖项,保证了我们只在数据变动时才去写磁盘,优雅!


四、 兄弟组件通信:间接的“曲线救国”
很多新手问:TodoInput 产生的数据,怎么传给 TodoList?
答案: 兄弟组件之间不能直接打招呼!它们必须通过共同的“老爹” App。

  1. TodoInput调用父组件传来的方法,把新数据传回父组件(子传父)。
  2. 父组件更新todos状态。
  3. 父组件把更新后的todos传给TodoList(父传子)。

这就是“父组件负责持有数据,管理数据”的核心原则。


五、 子父通信:自定义事件的“上报”
由于 React 的props 是只读的,子组件绝对不能直接修改父组件传过来的变量。
1. 子组件如何修改父组件的自由变量?
秘诀:父组件不仅把数据传给子,还把“修改数据的方法”也传过去。
JavaScript

// App.jsx 中 const addTodo = (text) => { setTodos([...todos, { id: Date.now(), // 使用时间戳作为唯一 ID text, completed: false, }]); } return ( <TodoInput onAdd={addTodo} /> // 传递方法 )

💡 超级关键点:唯一 ID。遍历数据(map)时必须有key。为什么?React 用虚拟 DOM 算法比对差异时,靠key识别哪个元素变了。如果用index,删掉中间一个元素会导致后续所有元素重绘,性能炸裂。这里我们用Date.now()快速生成唯一 ID。


六、 详解 TodoInput:模拟“双向绑定”
React 不支持v-model,因为它推崇“显式优于隐式”。我们要实现类似功能,需要通过单向绑定 + onChange 监听
JavaScript

const TodoInput = ({ onAdd }) => { const [inputValue, setInputValue] = useState(''); const handleSubmit = (e) => { e.preventDefault(); // 阻止表单默认提交刷新页面 if(!inputValue.trim()) return; onAdd(inputValue); // 调用父组件传来的函数 setInputValue(''); // 清空输入框 } return ( <form className="todo-input" onSubmit={handleSubmit}> <input type="text" value={inputValue} // 绑定状态 onChange={e => setInputValue(e.target.value)} // 监听输入 /> <button type="submit">Add</button> </form> ) }

逻辑闭环:状态改变 -> 触发onChange-> 更新inputValue-> 视图重新渲染。虽然麻烦一点,但每一步都清清楚楚!


七、 详解 TodoList:Props 的清晰解构
在子组件中处理props时,推荐直接在函数参数里或者函数体第一行进行解构。
JavaScript

const TodoList = (props) => { const { todos, onDelete, onToggle } = props; // 清晰的解构 // ... 后面直接使用 todos,而不是 props.todos }

这样做的好处是:一眼就能看出这个组件依赖哪些数据,代码阅读感拉满。
列表渲染与三目运算符
TodoList中,我们使用了大量的三目运算符来控制视图:
JavaScript

{todos.length === 0 ? ( <li className="empty">No todos yet!</li> ) : ( todos.map(...) )}

这是 React 的基本功。记住:React 的大括号{}里可以写任何 JS 表达式。三目运算符是实现条件渲染最干净的方式。


八、为什么 ID 必须是“唯一”的?
TodoList组件里,我们看到todos.map循环时,每个<li>都有一个key={todo.id}。很多新手为了省事会直接用数组的索引index,但这正是万恶之源
1. 为什么不能用 Index?
React 在更新 DOM 时,会通过key来判断哪些元素是新加的、哪些被删除了。

  • 情景模拟:如果你有三个任务 A、B、C,索引分别是 0、1、2。当你删掉了中间的 B,剩下的 A 和 C 索引就变成了 0 和 1。
  • React 的困惑:React 会以为你删掉了 C(原来的索引 2 没了),然后把 B 的内容改成了 C。这不仅浪费性能,在涉及表单输入或动画时,还会产生非常诡异的 UI Bug。

2. 代码中如何实现“唯一 ID”?
在我们的App.jsxaddTodo方法中,是这样处理的:

JavaScript

const addTodo = (text) => { setTodos([...todos, { // 💡 超级关键点:使用时间戳生成唯一 ID id: Date.now(), text, completed: false, }]); }

专业讲解:

  • Date.now():它返回自 1970 年 1 月 1 日 00:00:00 UTC 以来经过的毫秒数。对于像 TodoList 这种个人使用的单机应用,用户点击按钮的速度是不可能超过 1 毫秒一次的,所以这个数字在当前应用中是绝对唯一的。
  • 更专业的方案:在大型商业项目中,我们通常会使用crypto.randomUUID()或者uuid库来生成更长、更复杂、碰撞率几乎为零的字符串 ID。


3. 渲染时的“身份标识”
TodoList.jsx中:
JavaScript

{todos.map(todo => ( <li key={todo.id} className={todo.completed ? 'completed' : ''}> {/* ...内容 */} </li> ))}

有了这个todo.id,React 的Diff 算法(找差异的算法)就能像激光手术一样精准:它知道你只是删掉了 ID 为1734950400000的那一项,而其他项完全不需要重新渲染。


4. ID 的三大纪律

  1. 稳定性:ID 生成后就不应该变(所以不能用Math.random(),因为它每次渲染都会变)。
  2. 唯一性:在当前列表中,不能有两个相同的 ID。
  3. 预测性:通过 ID 我们可以快速在setTodos中定位数据,比如todos.filter(t => t.id !== id)

九、 数据流操作:添加、删除与切换
App.jsx中,我们定义了几个关键操作:

  1. 添加 (addTodo): 使用解构赋值[...todos, newTodo]保证数据的不可变性(Immutability)。不要用push
const addTodo = (text) => { setTodos([...todos, { id: Date.now(),// 时间戳 text, completed: false, }]); }
  1. 删除 (deleteTodo): 使用filter
    JavaScript
const deleteTodo = (id) => { setTodos(todos.filter(todo => todo.id !== id)); }
  1. 切换状态 (toggleTodo): 使用map
    JavaScript
const toggleTodo = (id) => { setTodos(todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo )); }
  1. 💡 专业术语:这里体现了"数据驱动视图&quot; 。子组件只需发出一个“请求”(调用 ID),由父组件统一更新数据,正确且高效。

十、 总结:React 通信全景图
通过这个项目,我们要记住 React 组件通信的三板斧:

  1. 父传子:通过props直接传。
  2. 子传父:父传一个 callback 函数给子,子在需要时调用。
  3. 兄弟传:状态提升到父组件,通过父组件当中转站。

为什么子组件不能直接修改数据?
因为“统一,正确”。如果每个子组件都能随意修改父组件的数据,调试代码时你会发现根本找不着是谁把数据改坏了。单向数据流保证了数据的可追溯性。


希望这篇文章能帮你搞定 React 组件通信!如果觉得有用,记得点赞、收藏、关注三连哦!我们下期再见!🚀

原文: https://juejin.cn/post/75869399
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/22 19:06:15

LCD1602字符显示原理:一文说清其内部结构与工作方式

LCD1602字符显示原理&#xff1a;从硬件到代码&#xff0c;彻底搞懂它的底层逻辑在嵌入式开发的早期阶段&#xff0c;你有没有遇到过这样的场景&#xff1f;MCU已经跑起来了&#xff0c;传感器数据也读到了&#xff0c;结果一到“把温度显示出来”这一步就卡住了——不是屏幕全…

作者头像 李华
网站建设 2026/1/22 18:37:55

无需大量算力!GPT-SoVITS轻量级训练方案出炉

无需大量算力&#xff01;GPT-SoVITS轻量级训练方案出炉 在虚拟主播直播间里&#xff0c;一个声音与真人几乎无异的AI正在流畅播报商品信息&#xff1b;而在另一间康复中心&#xff0c;一位因疾病失去发声能力的患者正通过一段病前录音重建自己的“声音”——这一切背后&#x…

作者头像 李华
网站建设 2026/1/23 13:00:31

vue3中使用echarts实现3D饼图(组件封装)

前言 之前那篇文章已经实现3D饼图效果&#xff0c;这次只是在其基础上进行了简单的组件封装。详情请看vue3中用echarts达到3D饼图的实现 效果演示 添加无数据时占位盒子。&#xff08;自行根据ui设计更换样式&#xff09; 封装组件 Pie3D.vue组件 <template><divv-i…

作者头像 李华
网站建设 2026/1/23 12:03:28

基于单片机粮仓温湿度检测控制系统设计

一、系统总体设计方案 本粮仓温湿度检测控制系统以单片机为核心&#xff0c;搭配温湿度传感器、数据存储模块、报警模块及通风除湿执行模块&#xff0c;构建 “检测 - 分析 - 控制 - 反馈” 的闭环系统&#xff0c;旨在实时监控粮仓内温湿度变化&#xff0c;预防粮食霉变、虫害…

作者头像 李华
网站建设 2026/1/24 18:09:22

在学习SQL注入或XSS这类具体漏洞时,如何设计一个高效的“理论+实践”学习循环?

建立高效的“理论实践”循环&#xff0c;正是能否真正掌握SQL注入或XSS这类Web安全核心漏洞的分水岭。下面这个框架&#xff0c;希望能帮助您将知识转化为真实的攻防能力。学习阶段核心目标关键任务/方法推荐工具/环境① 靶场环境搭建​准备好一个安全、隔离的实验平台配置集成…

作者头像 李华
网站建设 2026/1/23 16:53:18

GPT-SoVITS模型灰盒测试方法:介于黑盒与白盒之间的验证策略

GPT-SoVITS模型灰盒测试方法&#xff1a;介于黑盒与白盒之间的验证策略 在智能语音技术飞速发展的今天&#xff0c;个性化语音合成已不再是实验室里的“未来构想”&#xff0c;而是逐步渗透进教育、媒体、无障碍服务等实际场景。然而&#xff0c;当一个模型仅用一分钟语音就能克…

作者头像 李华