R10第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

第一个交互应用

第一个交互应用 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

前面九课的零件都齐了,这一课把它们装成第一个能独立运行的小应用:课程收藏列表。新增、删除、筛选、状态恢复一个不缺,边界情况全部覆盖。读完你应当有能力独立完成一个 CRUD 小工具,而不只是看懂别人的代码。

问题场景

需求很朴素:输入课程名收藏它;不想收藏了就删;列表长了要能按「全部 / 已收藏」切换;刷新页面后收藏还在。动手前先想清楚三个问题:数据模型是什么?有哪些交互、各自改哪块数据?哪些状态该恢复、怎么恢复?

答案是:数据模型就用一个数组 [{ id, title, done }];四个交互分别对应数组的增、删、改(done 标记)和筛选;恢复用 localStorage 存这个数组——组件结构反过来从数据模型推导。

核心解释

一、先立数据模型,再写组件。 列表应用的所有交互都是对同一个数组的操作,把它定稳了,组件树自然清晰:App 持有数组(状态提升给筛选控件用,R14 会展开讲),AddForm 收集输入,FilterTabs 显示切换,CourseItem 渲染单条。

二、每个交互都是一次「不可变更新」。 新增和删除在 R06/R13 都见过雏形,这里的关键是别在事件处理器里直接改数组:

function addCourse(title) {
  setCourses([...courses, { id: crypto.randomUUID(), title, done: false }]);
}
function removeCourse(id) {
  setCourses(courses.filter((c) => c.id !== id));
}

新数组交给 setState,React 才感知到变化;id 用 crypto.randomUUID() 现场生成(R06 的稳定标识)。

三、边界情况决定应用质感。 空输入不添加(trim() 后判空);重复收藏提示而不是塞进列表;全部删光后给空状态界面;筛选结果为空时说明「这个筛选下没有数据」而不是空白。

四、状态恢复只恢复数据,不恢复 UI 细节。 页面加载时从 localStorage 读数组还原;当前选中哪个筛选 tab 属于「会话内 UI 状态」,通常不用恢复。写入时机选在数据变化之后:

useEffect(() => {
  localStorage.setItem("faved-courses", JSON.stringify(courses));
}, [courses]);

示例拆解

实验区就是完整的收藏列表应用,关键点如下:

  • 数据流单行成立:AddForm 只上报输入,FilterTabs 只上报筛选值,全部状态在 App 集中管理;
  • 筛选不用存第二份数组:filter 是一个 state,列表是 courses.filter(...) 的派生结果(这个思路 R11 会正式展开);
  • localStorage 的读写都包了 try/catch——沙箱或隐私模式下存储可能不可用,降级成内存模式,应用照样能跑;
  • 空列表、空输入、重复项三个边界都有专属提示文案。

验证方式:添加两条、删除一条、切筛选、刷新沙箱(或重开实验),数据应当还原。

动手任务

打开本课实验区,完成三个小改动:

  1. 加「清空全部」按钮,弹出确认(用 confirm)后再删。
  2. 给课程项加「取消收藏」标记按钮(切换 done 字段),已收藏项显示对勾。
  3. 把「已收藏」筛选改成按 done 字段过滤,而不是仅靠存在性。

常见错误

  • 新增时直接 courses.push(...) 再 setState(courses):数组引用没变,React 不更新;每次都返回新数组。
  • 存进 localStorage 的是 JSX 或含函数的对象:JSON.stringify 会丢掉不可序列化的部分,只存纯数据。
  • 读取 localStorage 后忘了处理损坏数据:JSON.parse 抛异常让应用白屏;try/catch 解析,失败就回退初始数据。
  • 用输入框的 DOM 值直接当数据:受控输入(R15 会展开)让 React state 始终是唯一事实源,别从 DOM 里捞值。

检查与复盘

完成下方检查题确认理解。自查清单:

  • 能先写出数据模型和交互清单,再动手写组件
  • 四个数组操作(增删改筛)都能用不可变方式一口气写对
  • 知道哪些状态要恢复、哪些不恢复,读写 localStorage 都有降级方案

参考资料

动手实验
动手实验
预览
点击「运行」在隔离沙箱中预览
检查题共 1 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1读代码简单

阅读代码(React 18+)。页面初始显示什么?点击「切换」按钮两次之后呢?

function Lamp() {
  const [on, setOn] = useState(true);
  return (
    <div>
      <p>{on ? '灯亮着' : '灯灭了'}</p>
      <button onClick={() => setOn(!on)}>切换</button>
    </div>
  );
}

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...