React 学习站
React›核心›核心

Key 与列表复用

核心核心

一句话定义

Key 是数组内兄弟元素的稳定身份标识:diff 时 React 用它回答「这个新元素对应旧树的哪一个」,从而正确复用 DOM 与组件状态——而不是按位置盲目配对。

为什么重要

key 写错不会报错、多数时候还「看起来正常」,但会在插入/删除/排序时产生状态错位(输入框内容串行、勾选状态跑到别的行)。这是 React 面试与 code review 的必考题,也是最难靠测试发现的一类 bug。

前置知识

核心概念

  • 同层唯一:key 只需在兄弟元素之间唯一,不必全局唯一。
  • 稳定:同一元素跨渲染的 key 不能变;绝不用 Math.random()。
  • 可预测:来自数据本身的 id(数据库主键、uuid、业务编号)。
  • index 作 key:仅在「列表静态、永不重排/插入/删除」时安全。
  • key 强制重置:给任意元素加 key 可以强制 React 视其为不同元素(连 state 一起重置)——一个正式支持的技巧。

原理 / 机制

为什么 index key 在插入时会错位——逐步追踪:

jsx// 初始:["A", "B"],每个 Row 里有输入框,用户在第一行输入了 "hello"
const before = [
  <Row key={0} text="A" />,  // 挂着输入框 "hello"
  <Row key={1} text="B" />,
];
// 头部插入新项 C:
const after = [
  <Row key={0} text="C" />,  // React: "key=0 还在,复用!" → 把 props 换成 C
  <Row key={1} text="A" />,
  <Row key={2} text="B" />,  // 全新节点
];

结果:旧 key=0 的 DOM(含你输入的 "hello")被复用且改了文案;key=2 的 B 是新建节点。表面上「数据对了」,实际上输入内容留在了错误的行——因为身份匹配错了。若 key 用 text 或真实 id,React 会正确地「插入新节点、移动旧节点」,状态跟着身份走。

用 key 强制重置状态的合法用法:

jsx// 切换用户时表单彻底重来,而不必手动清空每个字段
<ProfileForm key={userId} userId={userId} />

直观类比

key 是身份证号,位置只是工位号。Diff 时 React 按身份证找人:换工位(移动)人还是那个人(DOM 与状态保留);用 index 作 key 等于「按工位认人」——新同事占了 3 号工位,公司档案就认他是原来 3 号的那位老员工,履历全串了。

实例 / 案例

排序 + 复选框的经典 demo(错误与正确各一遍):

jsxfunction List({ items, reverse }) {
  const shown = reverse ? [...items].reverse() : items;
  return (
    <ul>
      {shown.map(item => (
        // ❌ key={i}:反转后复选框状态不跟着数据走
        // ✅ key={item.id}
        <li key={item.id}>
          <input type="checkbox" />
          {item.title}
        </li>
      ))}
    </ul>
  );
}

自己复现一次:勾选第二行 → 点反转 → index key 下勾选「留在第二行」,id key 下勾选跟着原来的行走。前者就是 bug。

常见误区

  • key={Math.random()} / key={Date.now()}:每次渲染 key 全变 → 全列表销毁重建 → 状态丢失 + 性能灾难。
  • key 传给子组件内部使用:key 不是 prop,子组件里 props.key 是 undefined;需要值就另传 id={item.id}。
  • 以为 key「越全局唯一越好」:同层兄弟唯一即可;全局 uuid 反而暴露数据模型问题。
  • 在 .map() 外层包裹无 key 的 Fragment:<Fragment> 在数组里也需要 key(用 <React.Fragment key={id}>)。
  • 把「控制台有 key 警告」当噪音忽略:警告正是 index-key 陷阱的探测器。

自测题

  1. 完整描述「头部插入一项、index 作 key」时发生的三步匹配过程,并指出状态错位点。
  2. 什么时候 key={index} 是安全的?说出两个成立的条件。
  3. <ProfileForm key={userId} /> 为什么能「重置表单」?不用 key 的替代方案是什么(各自的缺点)?
  4. key 需要在整棵应用里唯一吗?「跨列表撞 key」会怎样?

与其他知识点的关系

  • 向前:diff 模型(kp-008)的直接应用。
  • 向后:与 kp-014 的「状态存哪里」呼应——state 存在「元素实例」上,key 定义实例身份;kp-028 审查清单里必查项;kp-022 里并发渲染对 key 稳定性要求更高。

延伸阅读

  • Keeping list items in order with key:<https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key>
  • Preserving State(state 与 key 的关系):<https://react.dev/learn/preserving-and-resetting-state>