数据、状态与 URL 驱动界面
状态是会影响界面并随交互变化的数据。把来源分清后,服务端数据、URL 条件、表单输入和派生结果才不会互相打架。
- 完成可分享的任务筛选,并能解释每一项状态由谁拥有、怎样改变、如何触发界面更新。
- 内容数据与界面结构分离
- 实现数据、状态与 URL 驱动界面后,应当完成可分享的任务筛选,并能解释每一项状态由谁拥有、怎样改变、如何触发界面更新,并保留类型检查、生产构建、浏览器行为和请求结果作为可重复检查的依据。
- 完成 4.4「React:用组件组织界面」
FOUNDATION
必须理解
状态(State)是会随交互变化并影响界面的数据。React 根据当前状态重新计算界面;如果同一事实被重复保存,多个副本就可能互相矛盾。
本课依次讲清内容数据与界面结构分离、状态、事件与重新渲染和URL 作为可分享、可前进后退的页面状态,最后通过“测试刷新、浏览器前进后退、空结果和重复点击”检查学习结果。
餐厅叫号屏不需要另外保存“还有几桌”等每个结果,只要有排队名单就能计算。保存原始事实,其他显示值尽量现算。
任务数组是核心状态;当前筛选条件可以进入 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
));
}阅读数据、状态与 URL 驱动界面示例时,完成数量由任务数组计算,切换时创建新数组,避免两个状态源互相不同步。
- useState 保存原始任务数组,completedCount 每次渲染都从数组计算。
- toggleTask 使用函数式更新取得最新数组,避免闭包中的旧值。
- map 创建新数组,仅为匹配 id 的任务创建新对象并反转 done。
点击某条任务后只有该项状态变化,完成数量同步更新;原数组没有被直接修改,也不存在需要额外同步的 completedCount 状态。
AI COLLABORATION
AI 如何参与
在数据、状态与 URL 驱动界面这一课,AI 负责根据真实材料解释内容数据与界面结构分离并指出遗漏,学习者负责控制范围、执行修改和核对类型检查、生产构建、浏览器行为和请求结果。
推荐协作顺序
- 1
把页面中的所有可变值列给 AI,让它先分类来源。
- 2
让它指出哪些值可以从其它数据计算,不要急着写 useState。
- 3
实现后沿一次点击追踪状态变化,确认只有一个可信来源。
请把这页数据分成服务端数据、URL 状态、组件状态、表单输入和可计算结果。逐项说明唯一来源与更新事件,再给最小实现顺序。不要重复保存可以计算的值。
每个值只有一个可信来源,completedCount 从 tasks 计算,filter 可由 URL 表达;数据、状态与 URL 驱动界面的人工验收必须回到真实页面、请求、终端、测试或数据结果,不能用 AI 的文字说明代替。
人工检查清单
- 确认 AI 没把可计算值复制进 state,也没有用 effect 在多个状态之间来回同步。
- 要求 AI 为每个值标出唯一所有者,并解释为何属于服务器、URL、页面或组件状态。
- 通过刷新、分享链接、前进后退和快速连续点击验证状态没有重复或丢失。
COMMON TRAPS
常见误区
下面三类问题会让任务看似完成,却经不起刷新、错误输入或真实环境检查。先看现象,再找原因和修正方法。
重复保存派生状态
- 你会看到
- 总数和列表不同步
- 为什么发生
- 同一事实有两份可写副本,只要一次更新漏掉其中一份,两者就会分叉。
- 怎样纠正
- 从唯一数据源即时计算
多个组件各存一份真相
- 你会看到
- 一次操作只更新其中一处
- 为什么发生
- 每个副本都有自己的更新时机,组件之间无法自动保持一致。
- 怎样纠正
- 把状态提升到共同拥有者
用副作用修补数据流
- 你会看到
- 更新顺序难以预测并出现循环
- 为什么发生
- 副作用在渲染后运行,若它又修改依赖项,更新顺序会变得隐蔽甚至循环。
- 怎样纠正
- 先检查是否可以在渲染或事件中直接完成
HANDS-ON
动手任务
完成可分享的任务筛选,并能解释每一项状态由谁拥有、怎样改变、如何触发界面更新。
- 完成 4.4「React:用组件组织界面」
跟着做
- 01
用 useState 保存本地任务数组和输入值。
- 02
通过不可变更新实现新增与切换完成。
- 03
用 filter 从 URL 或选择项决定显示列表。
- 04
使用 filter 和 reduce 计算可见任务与完成数量。
- 05
测试刷新、浏览器前进后退、空结果和重复点击。
完成可分享的任务筛选,并能解释每一项状态由谁拥有、怎样改变、如何触发界面更新。
把筛选条件写入 URL 查询参数,并分享链接验证另一窗口打开相同筛选。
离开本课前,自问四件事
- 内容数据与界面结构应如何分离?
- 为什么 React 状态更新后会重新渲染,而普通变量不会?
- 哪些信息适合放进 URL,哪些不适合?
- 完成任务数量为什么应从任务数组计算而不是单独保存?
确认完成后,会同步更新学习中心的课程学习进度。