React 列表渲染与 key

解释列表协调中 key 的身份作用,说明 index key 的风险与稳定 id 的工程实践。

#type / concept #status / growing #tech / dev / frame #resource / react

[!info] 关联笔记

React 列表渲染与 key

这个概念为什么出现

库存表格增删排序后,输入框“串行”、动画错位、组件状态贴错 SKU——根因常常是 key 没有代表稳定身份。key 不是“去掉 warning 的装饰”,而是协调器识别子节点的身份证。

[!abstract] 一句话理解 key 告诉 React:这一项是“同一个逻辑实体”还是“新实体”;身份错了,状态就会跟错人。

最小可运行示例

场景:可编辑的低库存列表

import { useState } from 'react'

type Row = { id: string; name: string }

export function LowStockEditor() {
  const [rows, setRows] = useState<Row[]>([
    { id: 'A-01', name: '缠绕膜' },
    { id: 'A-09', name: '防潮纸板' },
  ])

  function reverse() {
    setRows((prev) => [...prev].reverse())
  }

  return (
    <div>
      <button type="button" onClick={reverse}>反转排序</button>
      <ul>
        {rows.map((row, index) => (
          // 教学点:用稳定 id;若用 index,反转后输入状态会串到另一行
          <li key={row.id}>
            <label>
              {row.id}
              <input defaultValue={row.name} />
            </label>
            {/* 反例对比:key={index} */}
          </li>
        ))}
      </ul>
    </div>
  )
}

操作:在第一行输入备注 → 点反转 → 观察输入是否还贴在正确 SKU 上。

结合场景关注点

  1. 本地 state/非受控输入最容易暴露错误 key。
  2. 排序/过滤/插入中间元素时 index key 最危险。
  3. key 应在兄弟列表内唯一,不是全局 UUID 仪式。

核心模型

协调时 React 比较同层子节点:

  • 相同 type + 相同 key → 视为可复用更新
  • key 变了 → 更可能拆旧挂新,state 重置

边界

  • 静态永不重排的列表用 index 风险较低,但习惯上仍优先业务 id
  • key 不能神奇加速;错误 key 只会制造 bug
  • 不要用随机数每次渲染新 key

常见误区

[!warning] 常见误区:key={Math.random()} 每次都是新身份,组件不断卸载挂载,性能与状态双崩。

工程实践

  • 后端实体用主键;前端临时项用客户端稳定 id
  • 虚拟列表场景 key 仍要稳定
  • 动画库更依赖正确身份

本节总结

列表的正确性先于性能:先让 key 代表身份,再谈 memo 与虚拟化。

自测题

  1. 为什么过滤列表时 index key 会出问题?
  2. key 写在 <li> 还是子组件根上?
参考答案
  1. 过滤改变索引与实体对应关系,状态易贴错。
  2. 写在数组返回的最外层元素上(map 直接返回的根)。
创建于 2026/3/19 更新于 2026/7/15