React 入门怎么练?用任务列表理解组件、状态和更新

编程狮(w3cschool.cn) 2026-09-15 16:42:46 浏览数 (37)
反馈

React 入门可以围绕一张任务列表练习:组件负责显示,状态保存变化,事件把用户操作转成状态更新。你可能已经会写 JSX,却发现输入框内容改了,页面列表没有跟着变化,或者删除一项后勾选状态跑到了另一项。问题通常在数据如何保存和识别,而不是标签写得不够多。

任务列表入门数据从哪里来,怎样变

本文完成添加、勾选和删除三个动作,解释它们为什么都应从状态出发。示例使用 React 18.3.1 的浏览器开发构建,让初学者直接打开页面实验;这只是固定版本的教学环境,不是生产项目的部署建议。所有任务保存在页面内存,刷新后清空。

一、组件应表达一段完整界面

组件可以理解成接收数据、返回界面的一段函数。任务项组件接收任务对象和两个回调,知道怎样显示这一项,却不自行维护全局任务列表。列表属于父组件,父组件决定添加或删除后有哪些任务。

props 是父组件传给子组件的输入。不要在子组件里直接修改传入的任务对象,因为父组件仍持有原来的状态引用。把用户意图通过回调传回去,由持有状态的组件更新,数据来源会更清楚。

如果组件、属性和渲染还不熟悉,可以先读 React 教程。先理解组件边界,再考虑拆文件;文件数量并不能证明组件设计合理。

二、把完整任务列表跑起来

保存为 tasks.html,在可访问示例脚本服务的环境中打开。为减少额外工具,代码使用 React.createElement,它与 JSX 描述的元素关系相同,只是写法不同。

<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>编程狮 React 任务练习</title>
<script src="https://cdn.jsdelivr.net/npm/react@18.3.1/umd/react.development.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@18.3.1/umd/react-dom.development.js"></script>
<div id="root"></div>
<script>
  const h = React.createElement;
  function TaskItem({ task, onToggle, onRemove }) {
    return h('li', null,
      h('label', null,
        h('input', { type: 'checkbox', checked: task.done,
          onChange: () => onToggle(task.id) }), task.text),
      h('button', { type: 'button', onClick: () => onRemove(task.id) }, '删除'));
  }
  function App() {
    const [text, setText] = React.useState('');
    const [tasks, setTasks] = React.useState([]);
    const nextId = React.useRef(1);
    function add(event) {
      event.preventDefault();
      const value = text.trim();
      if (!value) return;
      const task = { id: nextId.current++, text: value, done: false };
      setTasks(previous => [...previous, task]);
      setText('');
    }
    function toggle(id) {
      setTasks(previous => previous.map(task =>
        task.id === id ? { ...task, done: !task.done } : task));
    }
    function remove(id) {
      setTasks(previous => previous.filter(task => task.id !== id));
    }
    const done = tasks.filter(task => task.done).length;
    return h('main', null,
      h('h1', null, '今天的学习任务'),
      h('form', { onSubmit: add },
        h('label', { htmlFor: 'task' }, '新任务'),
        h('input', { id: 'task', value: text, maxLength: 80,
          onChange: event => setText(event.target.value) }),
        h('button', { type: 'submit' }, '添加')),
      h('p', { role: 'status' }, `共 ${tasks.length} 项,完成 ${done} 项`),
      h('ul', null, tasks.map(task => h(TaskItem,
        { key: task.id, task, onToggle: toggle, onRemove: remove }))));
  }
  ReactDOM.createRoot(document.querySelector('#root')).render(h(App));
</script>

先输入“学习组件”并回车,再输入“练习状态”并点击添加。计数应变成两项。勾选第二项后完成数量为一,删除第一项后应只剩第二项,并保持勾选。

输入框的 value 来自 text 状态,onChange 把用户输入交给 setText,构成受控输入。仅给 value 却不更新状态,输入框看起来就会像无法编辑。任务为空白时不添加,是 add 里的业务检查,与按钮是否可点击是两个层次。

三、状态更新为什么要创建新数组

useState 返回当前值与更新函数。你直接 tasks.push 修改旧数组,并不能表达一次可靠的状态替换。示例添加时用展开语法创建新数组,删除时用 filter,勾选时用 map 找到目标后创建新对象。

数组复制不是深复制。只复制外层后再修改内部对象,旧状态里的同一个对象也会被改动。因此 toggle 同时创建新数组和被修改任务的新对象,未修改任务可以继续复用原对象。

更新依赖旧状态时,示例采用函数形式,把 previous 交给计算过程。这避免你根据某次渲染里捕获的旧数组计算下一步。回调应保持纯粹;本例把新 id 的分配放在事件函数中,而不是塞进状态更新回调。

数组 map、filter 与展开语法可结合 JavaScript 教程 理解。不要一遇到 React 更新问题就归因于框架;很多问题来自普通对象共享和数组修改方式。

四、列表身份与派生数据怎样处理

key 用于帮助 React 识别同一列表中的项目身份。任务删除、插入后,稳定 id 仍表示同一个任务;数组下标则表示当前位置。把位置当身份,在含输入或局部状态的列表里容易产生错位。

示例用递增计数生成本次页面会话中的 id,并用 useRef 保存计数器。它不会因为重新渲染而回到一。真实系统若有服务端任务 id,应优先使用业务身份;如果需要跨设备合并,本地递增数并不够用。

已完成数量通过 tasks 计算,不再额外保存一个 completedCount 状态。两个可以相互推导的数据分别维护,会增加同步错误:删除一个已完成任务时,列表变了,计数忘记减一。当前规模下直接计算足够清楚,不必先引入复杂优化。

用户动作 状态变化 需要保持的关系
添加任务 新数组增加对象 id 不与现有任务冲突
勾选任务 替换目标对象 其他任务值保持原样
删除任务 过滤目标 id 剩余任务身份不变

如果页面空白,先检查两个脚本是否成功加载,再看控制台错误。若界面出现但点击异常,检查事件回调与状态数据。不要把脚本加载失败和组件逻辑错误混在一起处理。

列表身份不等于文字,也不等于位置:同文字任务各有 id;删除一项不改变另一项身份

总结

删除操作还可以检查一次对象身份:先添加两条相同文字的任务,勾选其中一条,再删除另一条。剩余任务应保持自己的勾选状态,这说明程序按 id 而非文字寻找目标。若需求禁止重复任务,应在添加阶段增加业务判断,不能借用 key 规则替代去重。

React 入门的重点是状态所有权与更新路径:父组件持有任务数组,子组件接收数据并回传动作,列表用稳定 id 表达身份。界面上的计数是任务数组的计算结果,不需要另一份独立记录。

把三步操作跑通后,可以增加“只看已完成”的筛选功能。先保存筛选条件,再从原任务数组计算显示列表,不要把筛选结果覆盖原数组。这样切回全部时任务仍在,也能检验你是否真正理解了源数据和展示结果的区别。

延伸学习

  1. React 入门实例教程 可帮助梳理组件与状态基本概念。
  2. React 入门课程 可作为继续完成交互练习的学习路径。
  3. React 点击事件说明 可辅助理解事件回调的组织。

常见问题

Q:为什么 setTasks 后立刻打印仍可能是旧值?

当前事件函数读取的是本次渲染取得的状态。调用更新函数不会把已有变量立即改成新快照,应观察下一次渲染的界面或相应调试输出。

Q:任务文字相同能不能当成同一个 key?

不能保证。两条任务可以有相同文字,却是不同对象。身份应独立于可编辑的显示内容,用唯一且稳定的 id 更合适。

Q:刷新后任务消失是不是状态失效了?

不是。本例只保存内存状态,刷新会重新创建应用。持久化到本地存储或服务器属于下一步功能,要另行设计读取和保存过程。

技术依据:React 状态数组更新指南

0 人点赞