← [ BACK_TO_NOTES ]

React 列表渲染与 key

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

#concept #growing #frame #react

[!info] Related notes

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) => (
          // 教学点:用稳定 id;若用 index,反转后输入状态会串到另一行
          <li key={row.id}>
            <label>
              {row.id}
              <input defaultValue={row.name} />
            </label>
            {/* 反例实验:给 map 回调加 index 参数,再改成 key={index} */}
          </li>
        ))}
      </ul>
    </div>
  )
}

将组件挂载到 React + TypeScript 应用,在第一行修改输入值,再点反转:编辑值应仍属于 A-01。若改用 index key,原位置的非受控输入会被另一 SKU 复用,编辑值可能错配。defaultValue 只提供初始值,本例不把编辑结果保存回 rows。

结合场景关注点

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

核心模型

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

  • 同一父节点内,相同 type + 相同 key 允许复用组件/DOM 并保留局部状态;这不等于跳过 render
  • key 或 type 变化会形成新身份,旧子树卸载,新子树挂载,局部 state 重置
  • key 只在对应父节点的子列表内有意义;同 key 移到另一个父节点不能保证保留状态

边界

  • 不增删、不重排、没有需要随实体保留的局部状态时,index key 风险较低;动态列表优先稳定业务 id
  • key 不能神奇加速;错误 key 只会制造 bug
  • 不要每次渲染生成随机 key;临时项的 id 应在创建数据时生成并保存
  • key 是 React 使用的特殊信息,不作为普通 prop 传入组件;子组件需要 id 时应另外传入
  • map 返回组件时把 key 放在该组件元素上;多节点分组可用 <Fragment key={id}>,简写 <>...</> 不能携带 key

常见误区

[!warning] 常见误区:key={Math.random()} 每次渲染产生新身份,旧局部状态丢失,并增加卸载和挂载成本。

工程实践

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

验证记录:React 19.3.0 + jsdom 30.1.2 下,运行原组件并修改非受控输入,再反转列表;稳定 id 保留输入所属实体,index 反例将该输入复用于另一实体。此验证覆盖 DOM 身份与输入值,不覆盖动画库。

本节总结

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

自测题

  1. 为什么过滤列表时 index key 会出问题?
  2. key 写在 <li> 还是子组件根上?
参考答案
  1. 过滤改变索引与实体对应关系,状态易贴错。
  2. 写在 map 直接返回的元素/组件/Fragment 上;仅放在子组件内部的 li 上不能标识外层组件列表。

参考

创建于 2026/3/19 更新于 2026/10/17
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本