React 中 key 除了列表 Diff 还有什么作用?
简化版
key 不只用于列表 Diff,还决定同一位置的组件身份。key 变化时 React 会认为这是新组件,卸载旧实例并挂载新实例,因此可以用来重置表单、切换详情页状态或避免状态串用。
详细版
React 根据元素类型和 key 判断组件是否复用。
<UserForm key={userId} userId={userId} />
当 userId 从 1 变成 2,key 也变化,React 会重新创建 UserForm,内部 state 回到初始值。如果不加 key,同一位置同一类型组件会被复用,内部 state 可能保留。
面试中要讲清:key 是组件身份标识,不是只给 map 用;但也不能滥用随机 key,否则每次渲染都会重挂载,性能和状态都会出问题。
完整版教学
一、key 的本质是身份标识
React 渲染时会比较新旧元素。 如果同一位置元素类型相同、key 也相同,React 倾向复用旧组件实例。 如果 key 不同,就认为身份变了,需要卸载旧组件再挂载新组件。
type 相同 + key 相同 -> 复用实例
type 相同 + key 不同 -> 重建实例
type 不同 -> 重建实例
这解释了为什么 key 不只影响列表。 任何组件只要处在可比较的位置,key 都可能影响它是否被复用。
二、列表 Diff 只是最常见场景
在列表里,key 帮 React 识别每个元素对应哪条数据。 插入、删除、排序时,稳定 key 能让 React 少移动 DOM,避免状态错位。 这也是面试高频点。
items.map(item => (
<TodoItem key={item.id} item={item} />
))
| key 选择 | 插入排序表现 | 风险 |
|---|---|---|
| 稳定 id | 身份准确 | 推荐 |
| index | 顺序变化时错位 | 表单状态可能串 |
| random | 每次都变 | 全量重建 |
假设列表有 10 项,在开头插入 1 项。 用 id 时 React 知道旧 10 项只是位置后移;用 index 时,很多组件身份会被错误复用。
三、key 变化可以主动重置组件状态
有些时候复用不是你想要的。
例如用户详情页从 userId=1 切到 userId=2,表单内部草稿不应该沿用上一个用户。
这时可以给组件加上和业务身份相关的 key。
function UserPage({ userId }: { userId: string }) {
return <UserEditor key={userId} userId={userId} />
}
userId=1 -> UserEditor 实例 A
userId=2 -> key 变化 -> 卸载 A,挂载实例 B
这个技巧非常实用。 它比在 effect 里手动清空十几个 state 更可靠,因为重挂载会自然回到初始状态。
四、不加 key 时同位置同类型会复用
React 默认按树位置匹配组件。 如果两个分支都渲染同一个组件类型,React 可能复用实例,从而保留内部 state。 这有时会产生意外。
{mode === 'create'
? <Form mode="create" />
: <Form mode="edit" />}
上面两个分支都是 Form,位置也一样。
切换模式时内部输入状态可能不变。
如果希望重置,可以写:
<Form key={mode} mode={mode} />
这不是列表场景,却完全体现 key 的身份作用。
五、随机 key 是性能和状态杀手
有些人为了消除 key 警告,会写 key={Math.random()}。
这会让每次渲染 key 都不同,React 只能每次卸载并重建。
组件内部状态、DOM 焦点和缓存都会丢。
items.map(item => (
<TodoItem key={Math.random()} item={item} />
))
如果页面每秒刷新 5 次,10 个列表项每秒就可能重挂载 50 次。 这不仅浪费性能,还会让输入框光标、动画和订阅状态异常。
六、key 的作用域只在兄弟节点之间比较
key 不需要全局唯一,只需要在同一组兄弟节点中唯一。 不同列表里使用同样的 id 没问题。 这个点能避免把 key 设计得过于复杂。
记忆钩子:key 不是编号牌,是身份证;身份证变了,React 就当新人。
回答时先讲列表,再拔高到“组件身份”。 这样能覆盖常规考点,也能回答状态重置类追问。
七、常见误区与追问
- 误区:key 只在数组 map 中有用。 key 决定组件身份,非列表场景也能用来控制复用和重挂载。
- 误区:index 作为 key 永远没问题。 列表会插入、删除、排序时,index 容易导致状态错位。
- 误区:随机 key 可以快速解决警告。 随机 key 每次变化,会导致组件反复卸载和重建。
- 追问:如何用 key 重置表单? 给表单组件设置业务身份 key,例如
key={userId}或key={mode}。 - 追问:key 需要全局唯一吗? 不需要,只需要在同一层兄弟节点之间唯一。
- 追问:key 变化会触发生命周期吗? 会卸载旧组件并挂载新组件,effect 清理和重新执行都会发生。
八、加强记忆
key 这题别只停在列表 Diff。它本质是 React 判断组件身份的线索:稳定 key 帮助复用,变化 key 触发重挂载,随机 key 制造灾难。列表用业务 id,状态重置用业务身份,简单静态列表才可谨慎用 index。