Chapter 06
自测:三层题库
前五章建立了完整的心智模型。这一章用三层题检验它真的进了脑子,而不只是“读得很顺”。重点是最后的判别层——它逼你在跨章的新场景里做选择,这才是迁移。
怎么用这一章
- 三层梯度:概念层(回忆)→ 原理层(分析机制)→ 应用判别层(跨章场景)
- 所有答案集中在文末一个折叠块里。每题先自己写,再展开——“瞄一眼答案”会把检验变成又一次再读
- 判别层答不出,回对应章节重读;那才是模型没扎实的地方
A概念层(对应 01–02)
B原理层(对应 02–05)
- 协调把通用 O(n³) 树 diff 降到 O(n),靠的两条启发式假设各是什么?(§2.2)
- 本帧 count = 0,连写三次
setCount(count + 1),结果为什么只到 1?(§3.2) arr.push(x)之后setArr(arr),界面为什么纹丝不动?React 用什么判断“变没变”?(§3.3)- 为什么 Hook 不能写在
if或循环里?用“调用顺序认领槽”来解释。(§4.1) useEffect(fn, [dep])的 cleanup 函数在哪两个时刻运行?(§4.3)- React Compiler(2025-10 GA)让哪三个 API 基本不必再手写?它改变 state → UI 的语义吗?(§5.5)
C应用判别层 · 跨章场景
每道题都横跨至少两章。先判断“涉及哪几章的哪个概念”,再给方案。这是整份教程真正要检验的能力。
- S1 · 列表串状态:一个可拖拽排序的待办列表,每行带一个未提交的输入框。用数组下标当
key,拖动排序后,某行输入框里的草稿“串”到了别的行。这是什么问题?涉及哪两章的哪个概念?正确做法? - S2 · 定时器不动:一个
useEffect(空依赖)里开setInterval想每秒给 count 加 1,count 却停在 1。从“快照”和“依赖数组”两个角度解释,并给出两种修法及取舍。 - S3 · 选型权衡:要做一个每秒更新几十次的实时仪表盘,团队在 React 与 Solid(signals)之间犹豫。从“重跑 + diff 的代价”出发,说出 React 的潜在劣势、以及 React 19 时代用什么抵消它。
- S4 · ref 还是 state:需求是“记住用户上一次悬停的卡片 id,用于下次交互判断,但这个 id 不直接显示在界面上”。该用 state 还是 ref?如果改成“要把这个 id 显示出来”,结论怎么变?
- S5 · RSC 切分:一个“文章正文 + 底部点赞按钮 + 评论输入框”的页面,用 Server Components 该如何划分服务端 / 客户端?划分依据是哪一章的什么概念?
亲手画一张图
合上教程,在纸上画出 §1.2 的 UI = f(state) 循环——只画 4 个节点:state → 组件 f → UI → DOM → setState,再连回 state。
画完回到图 1.2 对照三件事:① setState 的箭头有没有指回 state?② 有没有漏掉“用户交互”这条触发入口?③ 你能不能在这张图上,指出第 3 章的“快照”发生在哪个节点、第 4 章的 effect 挂在哪一步之后?画得出来,这份心智模型才算真的是你的。
§全部答案(先做完再展开)
答案集中在这里。判别层尤其要先写出自己的版本——这一层的价值全在“自己先做选择”。
展开全部答案
概念层
- 把“计算并执行 DOM 的差异更新”转移给了 React。你只产出目标描述,怎么把 DOM 改成那样由 React 负责。
el是一个普通 JS 对象(React 元素,形如{ type: "h1", props: { children: "Hi" } }),页面上没有出现<h1>。它要被 React 渲染进根节点才会变成真实 DOM——写 JSX ≠ 改 DOM。- 因为数据所有权在拥有它的组件,props 是传下来的只读参数;子组件改 props 不会触发任何重渲染。唯一合法途径:调用父组件传下来的回调,由父组件
setState。 - 不是。重渲染 = 重新调用组件函数得到新描述;只有当新旧描述 diff 出差异时,提交阶段才改对应的真实 DOM。重渲染可以完全不改 DOM。
原理层
- ① 类型不同则整棵子树销毁重建,不深入比较;② 同位置同类型则复用同一 DOM、只更新变化属性并递归子节点。列表再用 key 提示稳定身份。
- 因为 count 是本帧快照、恒为 0,三次都是
setCount(0 + 1),入队的都是“把 state 设为 1”,后者覆盖前者。要累加需用函数式更新setCount(c => c + 1)。 - 因为
push改的是数组内部,引用没变;setArr拿到的新旧引用相同,React 用Object.is判定“没变”而跳过渲染(bailout)。要给新引用:setArr([...arr, x])。 - 因为 React 靠“第几次调用”把 Hook 对应到 fiber 上的第几个槽。放进
if,某次渲染少调一个,之后所有 Hook 的序号前移、对错槽,状态全乱。所以必须每次按相同顺序、相同数量调用。 - ① 下一次该 effect 因依赖变化重新运行之前(先清理旧的);② 组件卸载时(最后一次清理)。
useMemo/useCallback/React.memo。不改变语义——它只自动跳过没必要的重算,state → UI 的关系不变。
应用判别层
- S1(涉及 §2.3 key + §3 状态绑位置):下标当 key 让 React 按位置匹配,排序后位置变、下标也变,于是旧 DOM 节点(含未提交输入草稿)被复用给了新数据。正确做法:用每行数据自带的稳定 id 当
key,让匹配按身份而非位置进行。只想到“key 要稳定”而没意识到“输入草稿是绑在 DOM 节点位置上的状态”,就漏了第 2/3 章那一半。 - S2(涉及 §3.1 快照 + §4.3 依赖):空依赖让 effect 只在挂载时跑一次,那次闭包永远捕获首帧
count = 0,于是每秒都setCount(0 + 1)。修法一:函数式setCount(c => c + 1),不读快照、读队列上一个结果,空依赖也对。修法二:把count写进依赖,每次变化重建 interval、捕获新快照——但会频繁重建定时器,通常不如修法一。 - S3(涉及 §2.1 render 代价 + §5.2 备选):React 默认重跑组件 + diff,高频更新下可能做较多“算了又 diff”的工作,这是 signals(精确更新、无需 diff 整棵树)的相对优势所在。React 19 时代的抵消手段:React Compiler 自动记忆化跳过未变子树,加上把高频区域拆细、用 ref / 非受控减少 state 驱动的重渲染。只比较“谁快”而不谈“React 用什么抵消代价”,就只答了一半。
- S4(涉及 §4.2 ref + §3 state):不显示、只用于下次交互判断 → 用
ref(跨渲染保留且改它不触发重渲染,避免多余渲染)。一旦要把这个 id 显示出来,就必须改用state——ref 改了不触发渲染,界面不会更新。判据就是“它影不影响 UI”。 - S5(涉及 §1.2 纯函数 + §5.6 RSC):正文是纯展示、无 state,放服务端组件(直读数据、不进 bundle);点赞按钮带本地 state + 点击事件、评论输入框是受控输入,二者都需
"use client"放客户端。划分依据:“有没有 state / effect / 事件”,即第 1 章“纯函数 vs 有交互”的延伸。