数据、状态与 URL 驱动界面
现代界面不是不断手动操作页面,而是让页面根据数据和状态长成应该有的样子。
- 能从状态变化解释一个交互为什么更新界面。
- 内容数据与界面结构分离
- 制作可筛选列表,让筛选条件进入 URL,并处理加载、空结果和错误状态。
- 完成 4.3「React:用组件组织界面」
FOUNDATION
必须理解
现代界面不是不断手动操作页面,而是让页面根据数据和状态长成应该有的样子。
状态(State)是会随交互变化并影响界面的数据。React 根据当前状态重新计算界面;如果同一事实被重复保存,多个副本就可能互相矛盾。
餐厅叫号屏不需要另外保存“还有几桌”等每个结果,只要有排队名单就能计算。保存原始事实,其他显示值尽量现算。
任务数组是核心状态;当前筛选条件可以进入 URL;“已完成数量”由任务数组计算,不再单独维护一个容易失真的数字。
内容数据与界面结构分离
任务标题和完成状态属于内容数据;当前选中了哪个筛选、输入框写到哪里,则更像界面状态。先弄清一个值该由服务器、URL、页面还是小组件负责。
基础概念
内容数据描述产品中的事实,界面结构描述这些事实怎样呈现。把二者分离后,同一份任务数组可以生成列表、统计和筛选结果,而无需复制三份标记。
进一步理解
数据通常拥有稳定字段,例如 id、title、done;组件通过映射和条件渲染把它变成元素。改变数据会改变画面,改变布局不应反过来偷偷改业务事实。
每条可变记录需要稳定标识,React 才能在列表更新时追踪对应项。数组索引在排序或插入后会改变,不适合作为长期身份。
任务数组作为唯一事实来源,列表逐条渲染,完成数量通过过滤计算,空数组则显示空状态。
把 JSX 写进数据字段会把内容和表现绑死;把完成数量再次保存又会产生与任务数组不一致的第二份事实。
这一小节记住:保存最小事实,让界面和统计从事实计算出来。
状态、事件与重新渲染
点击事件会触发状态更新,React 再根据新状态画一次界面。别直接在原数组上改值,创建一份新数组更容易让 React 和你自己都看清变化。
基础概念
状态是组件需要在多次渲染之间记住、且变化后会影响界面的数据。事件触发状态更新,React 根据新状态再次计算组件树并更新必要的 DOM。
进一步理解
状态更新不是直接改当前变量,而是请求 React 安排下一次渲染。根据旧值计算新值时使用函数式更新,可以避免读取过期快照。
对象和数组应创建新值而不是原地修改,使变化可被可靠比较。共享状态放到最近共同父组件,避免多个组件各自维护不同版本。
点击任务按钮后,setTasks 根据 id 生成新数组,只替换目标任务;React 重新渲染并更新对应文字。
普通局部变量会在每次渲染重新计算,改变它也不会通知 React;状态则由 React 保存并触发新渲染。
这一小节记住:事件提出变化,状态保存事实,渲染把最新事实呈现出来。
URL 作为可分享、可前进后退的页面状态
像 ?filter=done 这样的 URL 可以把筛选状态保留下来,刷新、分享和前进后退都更自然。还没提交的输入文字通常只放组件里就够了。
基础概念
URL 可以保存适合分享、刷新和历史导航的页面状态,例如搜索词、筛选条件和页码。它是浏览器与用户都能看见的状态容器。
进一步理解
当页面结果由 URL 决定,复制地址即可重现视图,前进后退也能恢复选择。敏感信息、巨大对象和未提交输入不适合放进 URL。
组件本地状态适合短暂交互,服务器数据适合长期共享事实,URL 状态适合导航语义。先判断生命周期与共享范围,再选择位置。
`/tasks?filter=done` 表示只看已完成任务;刷新或发给同伴后仍得到相同筛选,而输入框正在编辑的草稿留在本地状态。
URL 参数不等于数据库数据,它可以描述当前视图,却不能替代服务端保存任务记录和权限判断。
这一小节记住:需要可分享、可刷新、可前进后退的视图选择,优先考虑 URL。
- 组件已从 React 导入 useState,并存在 Task 类型。
- tasks 是当前页面中任务事实的唯一所有者。
const [tasks, setTasks] = useState<Task[]>([]);
const completedCount = tasks.filter((task) => task.done).length;
function toggleTask(id: string) {
setTasks((current) => current.map((task) =>
task.id === id ? { ...task, done: !task.done } : task
));
}完成数量由任务数组计算,切换时创建新数组,避免两个状态源互相不同步。
- useState 保存原始任务数组,completedCount 每次渲染都从数组计算。
- toggleTask 使用函数式更新取得最新数组,避免闭包中的旧值。
- map 创建新数组,仅为匹配 id 的任务创建新对象并反转 done。
点击某条任务后只有该项状态变化,完成数量同步更新;原数组没有被直接修改,也不存在需要额外同步的 completedCount 状态。
AI COLLABORATION
AI 如何参与
让 AI 明确区分服务端数据、组件状态、URL 状态和临时输入,不要把所有东西都塞进 useState。
推荐协作顺序
- 1
把页面中的所有可变值列给 AI,让它先分类来源。
- 2
让它指出哪些值可以从其它数据计算,不要急着写 useState。
- 3
实现后沿一次点击追踪状态变化,确认只有一个可信来源。
任务应用有 tasks、filter、completedCount、inputValue、isDialogOpen 五个值。请判断它们分别应来自服务端数据、URL 状态、可计算值或组件状态,并说明理由。再给出最小数据流图,不使用 useEffect 同步重复状态。
每个值只有一个可信来源,completedCount 从 tasks 计算,filter 可由 URL 表达。
人工检查清单
- 确认 AI 没把可计算值复制进 state,也没有用 effect 在多个状态之间来回同步。
- 要求 AI 为每个值标出唯一所有者,并解释为何属于服务器、URL、页面或组件状态。
- 通过刷新、分享链接、前进后退和快速连续点击验证状态没有重复或丢失。
COMMON TRAPS
常见误区
错误不是需要隐藏的失败,而是帮助你看清系统边界的证据。下面三类问题在 AI 辅助学习中最常出现。
什么都放进 useState
- 你会看到
- 任务数组、完成数量和筛选后列表各保存一份,更新时经常不同步。
- 为什么发生
- 可计算值被重复保存后,需要每次同步更新;漏掉一次就会出现两个互相矛盾的真相。
- 怎样纠正
- 只保存原始事实,完成数量和可见列表在渲染时计算。
直接修改原数组
- 你会看到
- 任务对象已经变了,React 却没有按预期刷新。
- 为什么发生
- 多个组件各自保存同一任务列表,局部更新不会自动传播,界面会显示不同版本。
- 怎样纠正
- 通过 map、filter 或展开语法创建新数组和新对象。
刷新页面后筛选消失
- 你会看到
- 需要分享的筛选条件只存在组件内存。
- 为什么发生
- 副作用常被用来事后同步本可直接计算的数据,增加额外渲染与循环依赖。
- 怎样纠正
- 适合分享和前进后退的状态放进 URL 查询参数。
HANDS-ON
动手任务
制作可筛选列表,让筛选条件进入 URL,并处理加载、空结果和错误状态。
- 完成 4.3「React:用组件组织界面」
跟着做
- 01
用 useState 保存本地任务数组和输入值。
- 02
通过不可变更新实现新增与切换完成。
- 03
用 filter 从 URL 或选择项决定显示列表。
- 04
使用 filter 和 reduce 计算可见任务与完成数量。
- 05
测试刷新、浏览器前进后退、空结果和重复点击。
能从状态变化解释一个交互为什么更新界面。
把筛选条件写入 URL 查询参数,并分享链接验证另一窗口打开相同筛选。
离开本课前,自问四件事
- 内容数据与界面结构应如何分离?
- 为什么 React 状态更新后会重新渲染,而普通变量不会?
- 哪些信息适合放进 URL,哪些不适合?
- 完成任务数量为什么应从任务数组计算而不是单独保存?
确认完成后,会同步更新学习中心的课程学习进度。