React TodoList 通俗讲解|彻底搞懂父子组件通信与状态管理

很多初学 React 的同学绕不明白:子组件为啥不能直接修改父的数据?数组对象更新为什么不能直接改?今天用 TodoList 案例,大白话讲清楚。

先记住 2 条铁律✅ 规则 1:数据统一放在父组件 App 里保管,子组件不能直接动手改这份数据✅ 规则 2:子想要修改数据 → 调用父给的函数,让父自己去改数据

1. 第一块: todos 是什么?

jsx

php 复制代码
const [todos, setTodos] = useState([
    { id: 1, text: '吃饭', completed: false },
    { id: 2, text: '睡觉', completed: false },
    { id: 3, text: '打豆豆', completed: true }
])

把它理解成:App(爸爸)手里拿着一张清单todos,记录所有待办事项。

  • todos = 清单本身(只能看,不能直接改)
  • setTodos = 唯一能修改清单的工具

❌ 错误写法:直接 push、直接修改里面对象。React 看不见改动,页面不会刷新。✅ 正确写法:必须使用 setTodos 换一份全新清单。

每条待办的含义:

  • id:编号,用来区分每一条任务
  • text:任务文字
  • completed:是否完成(true = 完成,false = 未完成)

2. 函数 1:addTodo 添加任务

jsx

vbnet 复制代码
const addTodo = (text) => {
  if (text.trim() === '') return 
  setTodos([
    {id: +Date.now(), text, completed: false },
    ...todos
  ])
}

流程

  1. TodoInput(输入框子组件,孩子) 用户输入文字,按下回车
  2. 孩子没有权限直接改清单,于是调用爸爸给的 onAdd,把输入文字传给爸爸
  3. 进入 addTodo
  • if (text.trim() === '') return:如果输入是空的,直接不处理,禁止空白任务
  • +Date.now():拿当前时间戳当做新任务独一无二的编号
  • [新任务, ...todos]:造一张全新清单,新任务放最上面,旧任务原样带上
  • setTodos(新清单):爸爸替换手里的旧清单 → 页面自动刷新

...todos 大白话:把旧清单里所有任务全部复制出来。

3. 函数 2:toggleTodo 勾选 / 取消任务

jsx

ini 复制代码
const toggleTodo = (id) => {
  setTodos(todos.map(todo => 
    todo.id === id 
      ? { ...todo, completed: !todo.completed}
      : todo
  ))
}

流程

  1. 用户点击任务前面的复选框(子组件 TodoList)
  2. 子组件把这条任务的 id 传给爸爸提供的 onToggle
  3. map 循环旧清单,生成一张全新清单
  • 如果当前循环这条任务 id 和点击的 id 一致:复制这条任务 {...todo},把完成状态取反 !todo.completed
  • 不匹配:直接原样保留这条任务
  1. setTodos 替换清单,页面刷新

❌ 禁止直接写 todo.completed = !todo.completed。不能在原对象上修改!

4. 函数 3:deleteTodo 删除任务

jsx

ini 复制代码
const deleteTodo = (id) => {
  setTodos(todos.filter(todo => todo.id !== id))
}

流程

  1. 用户点击某条任务的删除按钮(子组件 TodoList)
  2. 子组件把要删除任务的 id 传给爸爸 onDelete
  3. filter 筛选:只保留 id 不等于要删除 id 的任务,自动剔除目标任务
  4. 返回筛选后的全新清单,setTodos 更新页面

5. 组件调用部分(父子沟通核心)

jsx

ini 复制代码
<TodoInput onAdd={addTodo}/>

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

<TodoStats todos={todos}/>

形象比喻:

  • <TodoInput> 输入框孩子:爸爸给它一个工具 onAdd。孩子输入内容后,喊爸爸:"帮我新增一条任务!"
  • <TodoList> 列表孩子:爸爸把清单todos复印件交给它展示。同时给它两个工具:onToggle(切换完成)、onDelete(删除)。孩子点击勾选 / 删除,只能通知爸爸干活,自己不能修改清单原件。
  • <TodoStats> 统计孩子:只拿清单复印件,单纯展示已完成多少条,不需要修改,所以不给工具函数。

核心思想一句话总结所有原始数据清单,只保存在父组件 App。子组件只负责展示页面。子想要修改数据,只能调用父提供的函数,由父组件统一修改数据。

完整数据流例子(新增一条任务)

  1. 用户在输入框打字:学习React
  2. 回车 → TodoInput 执行 onAdd("学习React")
  3. 父组件 addTodo 执行,生成新任务,构建全新任务清单
  4. setTodos 更新状态
  5. App 组件刷新,TodoList、TodoStats 拿到新清单,页面自动更新

补充:配套子组件完整代码(可直接运行)

TodoInput.jsx

jsx

javascript 复制代码
import { useState } from 'react'
export default function TodoInput({onAdd}){
  const [value, setValue] = useState('')
  const handleKeyDown = (e)=>{
    if(e.key === 'Enter'){
      onAdd(value)
      setValue('')
    }
  }
  return (
    <input 
      value={value}
      onChange={(e)=>setValue(e.target.value)}
      onKeyDown={handleKeyDown}
      placeholder="请输入待办事项,回车添加"
    />
  )
}

TodoList.jsx

jsx

javascript 复制代码
export default function TodoList({todos,onToggle,onDelete}){
  return (
    <ul>
      {todos.map(item=>(
        <li key={item.id}>
          <input 
            type="checkbox" 
            checked={item.completed}
            onChange={()=>onToggle(item.id)}
          />
          <span style={{textDecoration:item.completed?'line-through':'none'}}>
            {item.text}
          </span>
          <button onClick={()=>onDelete(item.id)}>删除</button>
        </li>
      ))}
    </ul>
  )
}

TodoStats.jsx

jsx

javascript 复制代码
export default function TodoStats({todos}){
  const total = todos.length
  const finished = todos.filter(i=>i.completed).length
  return <div>总共:{total}条,已完成:{finished}条</div>
}

小结

TodoList 看着简单,但是包含 React 最核心的单向数据流、状态提升、不可变数据思想。记住:数据向上集中,交互向下传递回调。所有子组件不要尝试直接修改 props 数据,通知父组件处理状态更新,是标准写法。

相关推荐
光影少年11 小时前
Fabric渲染流程
前端·react native·react.js
光影少年12 小时前
RN启动流程(bundle加载→Bridge初始化→首屏渲染)
前端·react native·react.js
卡布鲁6 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
用户298698530146 天前
在 React 中用 JavaScript 将 Word 转换为文本
前端·javascript·react.js
狗狗狗狗狗乐啊7 天前
搭一个 AI 对话工作台 AChat:从 0 到可用的完整记录(一)
python·react.js·ai编程
晓得迷路了7 天前
栗子前端技术周刊第 147 期 - React Router 8.4、Vite 云服务攻击、pnpm 12.4...
前端·javascript·react.js
神秘的猪头8 天前
React 脱围机制(二):重新理解 useEffect——它不是生命周期,而是同步外部系统
react.js
卡布鲁8 天前
React useMemo 与 useCallback 完全指南:原理、场景与避坑
前端·react.js
右耳朵猫AI8 天前
Web前端周刊2026W38 | React 19.3 发布、StyleX 深潜、jsdom 30.1 提速
前端·javascript·react.js·typescript·node.js
晚安日记wanna9 天前
React 为何不做双端 diff?Vue 与 React 更新逻辑的三层差异
前端·react.js·面试