React 警告:Each child in a list should have a unique “key” prop
一句话总结:用「唯一 ID + 稳定不变 + 不用索引」三件套,让列表渲染既稳定又高效,黄字警告瞬间消失!
正文目录
- 警告含义:React 在挑剔什么「key」?
- 5 大高频翻车场景 & 修复代码
- 万能兜底:key 的选择策略
- 预防 checklist(不再踩坑)
- 一句话总结
一、警告含义:React 在挑剔什么「key」?
当你在控制台看到:
Warning: Each child in a list should have a unique "key" prop.React 在告诉你:
「你在用 map 渲染列表时,每个子元素缺少一个独一无二的 key 属性。没有 key,React 无法高效地追踪哪些元素变了、哪些新增了、哪些删除了。」
没有 key 的后果:
- React 强制全量重新渲染列表,性能严重下降
- 列表项有状态(如输入框内容)时,状态会错位
- 动画效果异常,DOM 复用出错
本质:React 需要 key 来做高效的 Diff 比对。
二、5 大高频翻车场景 & 修复代码
① 完全没有写 key
// ❌ 没有 key function TodoList({ todos }) { return ( <ul> {todos.map(todo => ( <li>{todo.text}</li> // ❌ 缺少 key ))} </ul> ); }修复:加上 key,使用数据的唯一 ID
// ✅ 使用唯一 ID 作为 key function TodoList({ todos }) { return ( <ul> {todos.map(todo => ( <li key={todo.id}>{todo.text}</li> // ✅ 唯一 key ))} </ul> ); }② 使用数组索引(index)作为 key
// ❌ 用 index 作为 key function TodoList({ todos }) { return ( <ul> {todos.map((todo, index) => ( <li key={index}> // ❌ index 作为 key <input defaultValue={todo.text} /> </li> ))} </ul> ); }当你在列表头部插入/删除/排序时,index key 会导致严重问题:每个列表项的 key 都变了,React 会错误地复用 DOM 节点,输入框内容会错位。
修复:使用数据的唯一 ID
// ✅ 使用数据唯一 ID function TodoList({ todos }) { return ( <ul> {todos.map(todo => ( <li key={todo.id}> // ✅ 稳定的唯一 ID <input defaultValue={todo.text} /> </li> ))} </ul> ); }③ 使用随机值作为 key
// ❌ 用 Math.random() 作为 key function ProductList({ products }) { return ( <div> {products.map(product => ( <div key={Math.random()}> // ❌ 每次渲染都变 → 全部重新挂载 {product.name} </div> ))} </div> ); }使用随机值和没写 key 一样糟糕。每次渲染时 key 都不同,React 会认为所有的旧元素都被删除了,新的都是新增的,导致:
- 所有组件都被卸载再重新挂载
- 丢失所有内部状态
- 动画效果全部重置
修复:使用稳定唯一 ID
// ✅ 使用稳定的唯一 ID function ProductList({ products }) { return ( <div> {products.map(product => ( <div key={product.id}> // ✅ 稳定唯一 {product.name} </div> ))} </div> ); }④ key 不唯一(多个元素共享同一个 key)
// ❌ 多个元素用了相同的 key function CategoryList({ items }) { return ( <div> {items.map(item => ( <div key={item.category}> // ❌ 同一分类多个 item,key 重复 {item.name} </div> ))} </div> ); }当两个元素共享同一个 key 时,React 会困惑:它不知道该保留哪个、该删除哪个。这会导致渲染错乱。
修复:确保每个元素都有独一无二的 key
// ✅ 使用 item 级别的唯一 ID function CategoryList({ items }) { return ( <div> {items.map(item => ( <div key={item.id}> // ✅ 每个 item 有唯一 ID [{item.category}] {item.name} </div> ))} </div> ); }⑤ 使用对象引用作为 key(每次都变)
// ❌ 用对象引用作为 key → 每次渲染都是新引用 function List({ data }) { const items = data.map(d => ({ ...d, label: d.name.toUpperCase() })); return ( <div> {items.map(item => ( <div key={item}> // ❌ 对象引用每次都不同 {item.label} </div> ))} </div> ); }每次渲染items都是全新的对象数组,用item对象作为 key 等同于没用 key。
修复:使用基本类型的唯一标识
// ✅ 使用基本类型 ID function List({ data }) { const items = data.map(d => ({ ...d, label: d.name.toUpperCase() })); return ( <div> {items.map(item => ( <div key={item.id}> // ✅ 基本类型 ID,稳定不变 {item.label} </div> ))} </div> ); }三、万能兜底:key 的选择策略
| 场景 | 推荐 key | 不推荐 |
|---|---|---|
| 有后端唯一 ID | item.id | index、random |
| 无后端 ID,有唯一字段 | item.email/item.slug | index |
| 组合唯一值 | ${item.category}-${item.id} | 单一非唯一字段 |
| 无任何唯一字段(迫不得已) | crypto.randomUUID()在数据创建时生成 | index、Math.random() |
| 纯静态列表(永远不增删改) | 勉强可用 index | random、对象引用 |
万能模板:
function SafeList({ items }) { return ( <ul> {items.map(item => ( // ✅ 三原则:唯一 + 稳定 + 基本类型 <li key={item.id ?? crypto.randomUUID()}> {item.content} </li> ))} </ul> ); }重要:
crypto.randomUUID()只在数据首次创建时调用一次,千万不要放在 map 里每次都生成!
四、预防 checklist
- 所有
.map()渲染的列表元素必须有 key - key 必须是唯一的(列表内不重复)
- key 必须是稳定的(数据不变化 key 就不变)
- key 必须是基本类型(string 或 number,不是对象)
- 绝对不用index 作为动态列表的 key
- 绝对不用Math.random() / Date.now() 作为 key
- 控制台「Each child in a list should have a unique “key”」= 立即给 map 内的顶层元素加上 key
五、一句话总结
「Each child in a list should have a unique “key” prop」= 列表中缺少稳定唯一的标识。
用「唯一 ID + 稳定不变 + 基本类型」三件套,让列表 Diff 精准高效,警告永远消失!
最后问候亲爱的朋友们,并邀请你们阅读我的全新著作
📚 《 React开发实践:掌握Redux与Hooks应用 》