列表渲染与稳定标识
列表渲染与稳定标识 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
列表渲染人人会写,真正的坑在 key:为什么 React 需要它、下标当 key 什么时候出事、什么才算稳定标识。这一课用一个可重排的任务列表让你亲眼看到错位,读完你能为任何列表选出正确的 key。
问题场景
任务列表带一个需求:每项旁边有个输入框,用来记备注。测试反馈了一个诡异的 bug——把第二项拖到第一位后,原来给「任务 B」写的备注跑到「任务 A」那一行去了:
{tasks.map((task, index) => (
<TaskRow key={index} task={task} />
))}
数据明明是对的(每项的内容和备注都还在自己的对象里),界面却错位了。问题就出在 key={index}。
核心解释
key 是 React 识别列表项身份的依据。 更新列表时,React 拿新旧两组元素按 key 配对:key 相同的就是「同一个元素」,只更新变化的部分;DOM 节点和组件状态也会跟着 key 走。key 决定了「谁是谁」,而不是「第几个」。
用数组下标当 key,等于告诉 React「第 0 项就是第 0 项」。列表重排后,tasks[0] 的内容从任务 A 变成了任务 B,React 却认为 key=0 的那一行没换人,只是属性变了,于是复用原来的 DOM 节点和输入框——输入框里用户敲的字不属于 React 数据,跟着 DOM 节点留在了原位,内容就错位了。
正确的 key 要求两条:在列表内唯一,且在元素的整个生命周期内稳定。业务数据里现成的唯一标识(id、订单号、slug)就是干这个的:
{tasks.map((task) => (
<TaskRow key={task.id} task={task} />
))}
排序、插入、删除都按 id 配对:任务 B 挪到第一位,React 知道是「id=B 的元素换了位置」,把整行 DOM 一起搬过去,输入框里的字跟着走,自然不错位。
用下标还有一个更隐蔽的坑:删除中间一项后,后面所有项的下标集体前移,React 会认为「最后一项被删了」,列表项自身的 state(比如展开/折叠标记)会落在错误的元素上。数据没有唯一 id 时,可以在创建时就生成一个(crypto.randomUUID()),而不是用渲染时的下标。
示例拆解
实验区是同一个任务列表的两种实现,关键点如下:
- 每行包含任务名和一个预填了内容的备注输入框(用
defaultValue让文字留在 DOM 里,模拟用户已输入); - 下标 key 版与 id key 版并排,各自有「插入到最前」「删除第一项」「随机排序」三个按钮,数据源相同、操作同步;
- 预填内容设计成「第 1 行备注」这种与位置绑定的文字,方便观察错位;
- 每次操作后先在下标版输入框里改几个字,再做重排操作,对比两边的输入框内容。
验证方式:下标版重排后备注文字停在原位置(DOM 没动);id 版重排后文字跟着任务走(DOM 整行搬移)。
动手任务
打开本课实验区,完成三个操作观察:
- 两边都在第一行输入框里输入自己的标记(如「AAA」),点「随机排序」,看「AAA」落在哪一边的任务上。
- 点「删除第一项」,观察下标版哪一行的 DOM 被删、id 版哪一行被删。
- 把下标版改成 id key(在实验代码里搜
key={index}),确认问题消失——这就是修复本身。
常见错误
- 用数组下标做会重排列表的 key:插入、删除、排序后输入框内容与组件状态错位;改用稳定业务 id。
- 用随机数当 key:每次渲染 key 都不同,React 每次都销毁重建全部节点,输入框直接清空、动画失效。
- 用
Math.random()或Date.now()在渲染时生成 id:同一条数据两次渲染身份不同,等于没有 key;id 要在数据创建时生成并存住。 - key 取值不唯一:重复 key 触发控制台警告,配对行为不可预测;实在没有唯一字段就用
crypto.randomUUID()补一个。
检查与复盘
完成下方检查题确认理解。自查清单:
- 能解释 key 的作用机制:React 按 key 做新旧配对,决定 DOM 与状态复用
- 知道下标 key 只在「列表永不重排、项无自身状态」时无害,谨慎使用
- 数据缺 id 时会在创建阶段生成稳定标识,而不是渲染时现造
参考资料
- React 官方文档:用 key 保持列表项身份 —— key 的规则与下标 key 的陷阱
- React 官方文档: 与状态 —— 输入框的 DOM 状态与 React 状态的关系
读完这一课了?
标记完成后,工作台会安排接下来的学习。