一、前言
在 React 开发中,key 是一个非常关键但又常常被误解的属性。它出现在我们使用 .map() 渲染列表的时候,虽然不是 DOM 属性,但它的作用却非常核心:帮助 React 高效地识别列表中每个元素的身份。
本文将带你从零开始理解 key 的作用、为什么不能使用 index 作为 key、以及如何正确使用 key 来优化你的 React 应用。
二、什么是 key?
在 React 中,当你使用 .map() 方法渲染一个列表时,你通常会看到类似这样的代码:
{items.map((item) => (
<div key={item.id}>{item.name}</div>
))}
其中的 key 属性是 React 的一个特殊属性,它不是传递给组件的 props,而是 React 内部用来:
- 识别哪些元素发生了变化、被添加了或被删除了。
- 在虚拟 DOM 的 diff 算法中用于优化更新效率。
key是 React 用来识别列表中每个元素身份的“身份证”。
三、如果不写 key,React 会怎么做?
如果你没有手动指定 key,React 默认会使用数组的索引(index)作为 key。
例如:
{items.map((item) => (
<div>{item.name}</div>
))}
React 会自动处理默认索引,等价于:
{items.map((item, index) => (
<div key={index}>{item.name}</div>
))}
默认索引,虽然看起来没问题,但这种写法在某些情况下会导致性能问题和状态混乱。
四、默认索引'index'会导致什么问题呢?
❌ 问题 1:列表顺序变化时,key 也会变化
假设你有如下数据:
[
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' }
]
初始渲染时,每个元素的 key 是:
- 张三 → key=0
- 李四 → key=1
- 王五 → key=2
如果你将顺序改为:
[
{ id: 3, name: '王五' },
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
]
React 会认为:
- 王五 → key=0(新组件)
- 张三 → key=1(新组件)
- 李四 → key=2(新组件)
于是 React 会认为所有组件都变了,导致整个列表重新渲染!
但实际上,这些组件只是顺序变了而已,内容并没有变。
没看懂?没事,下文有实战演示
❌ 问题 2:状态丢失
如果你的列表项中包含状态(比如输入框、展开/收起状态),使用 index 作为 key 会导致:
- React 无法正确识别哪个状态对应哪个组件。
- 结果:输入框内容错乱、折叠面板状态混乱、动画异常等。
五、什么时候可以用 默认索引index 作为 key?
虽然不推荐,但在以下情况可以使用 index:
- ✅ 列表是静态的(不会排序、增删)。
- ✅ 列表项不需要维护状态(比如没有输入框、动画、展开收起等)。
- ✅ 列表只是展示内容,不涉及交互。
const list = ['首页', '关于', '联系'];
{list.map((item) => (
<div key={index}>{item}</div> // 可以接受
))}
六、正确的 key 应该满足什么条件?
- ✅ 唯一:不能重复。
- ✅ 稳定:即使顺序变化,key 也不应该变。
- ✅ 可预测:比如使用数据库中的
id。
{items.map((item) => (
<div key={item.id}>{item.name}</div> // 推荐写法
))}
七、实战演示:插入新元素到列表(注意:插入到表头和表尾有区别)
代码
function App() {
const [todos, setTodos] = useState([
{
id: 1,
title: 'todo1',
}, {
id: 2,
title: 'todo2',
}, {
id: 3,
title: 'todo3',
}
])
useEffect(() => {
setTimeout(() => {
setTodos(prev=>[...prev,{
id:4,
title:'标题-改二'
}])
setTodos(prev => [{
id: 4,
title: '标题4'
},
...prev
])
}, 5000)
}, [])
return (
<>
{
todos.map((todo) => (
<li key={todo}>
{todo.title}
</li>
))
}
</>
)
}
插入到表尾:
setTodos(prev=>[
...prev,{
id:4,
title:'标题-改二'
}])
并不影响,不会全部渲染,只渲染第四个。
插入到表头:
setTodos(prev => [{
id: 4,
title: '标题4'
}, ...prev])
这段代码在 5 秒后将一个新的 todo 项目插入到列表的最前面。 然后默认索引颠倒了,全部重新渲染,影响了性能
✅ 使用 todo.id 作为 key 的好处:
- 即使顺序变化,每个 todo 的
key保持不变。 - React 能准确识别哪些元素是新增的,哪些是已有的。
- 保证最小的重渲染,提升性能。
❌ 如果默认索引 作为 key:
- 可能会导致所有后续元素的
key都会改变。 - React 会误认为这些元素都发生了变化,导致不必要的重渲染。
- 如果列表项中有状态,还会导致状态丢失。
回顾:
key是 React 用来识别列表中每个元素身份的“身份证”。如果你不写key,React 会默认使用index,但这在列表顺序变化或需要维护状态时可能会导致问题。所以,推荐始终使用唯一且稳定的值(如id)作为key。