• 首页
  • 博客
  • 标签
  • 联系
街街的脏书包
  • 首页
  • 博客
  • 标签
  • 联系
联系

Copyright © 2026 - odaneo.com 保留所有权利

React 中 key 的作用与使用误区

前端React2026-01-29 01:47

React diff 算法在 key 不变时的行为

  • 复用 DOM 节点
    • key 相同且标签类型相同,不会销毁旧 DOM,而是拿旧的继续用。但是,如果标签类型不同,节点仍然会销毁重建。
  • 更新属性 / 内容
    • 文本节点会更新 textContent。标签属性(className, style 等)会更新。事件绑定会更新(比如 onClick)。
  • 更新 children
    • 如果子节点变了,继续递归 diff。
  • 保留状态
    • 复用 input 时不会清空用户输入(非受控组件),video、canvas 的内部状态也不会自动重置。其他比如 checkbox 的勾选、input 的光标位置 / 已输入内容、video 的播放进度、audio 的播放时间、canvas 的绘制内容等,也算 DOM 内部状态,会保留。需要注意的是,React 组件实例 & state,也会保留。

关键点1:key 相同,不会自动清空 DOM 内部状态

export default function App() { const [items, setItems] = useState(["苹果", "香蕉", "橘子"]); return ( <div> {items.map((item, index) => ( <label key={index}> <input type="checkbox" /> {item} </label> ))} <br /> <button onClick={() => setItems(["香蕉", "橘子"])}>删除苹果</button> </div> ); }
如果用户勾选了香蕉,那么这个 checkbox 的勾选状态算 DOM 内部状态(非受控组件)。
[ ] 苹果 [✔] 香蕉 [ ] 橘子
删除“苹果”后,勾选跑到橘子上,造成错位:
[ ] 香蕉 [✔] 橘子

关键点2:key 相同,不会自动重置组件实例,state 会被保留

function Counter({ label }) { const [count, setCount] = useState(0); return ( <div> {label}: {count} <button onClick={() => setCount(count + 1)}>+1</button> </div> ); } export default function App() { const [showFirst, setShowFirst] = useState(true); return ( <div> {showFirst ? <Counter key="same" label="苹果" /> : <Counter key="same" label="香蕉" />} <button onClick={() => setShowFirst(!showFirst)}>切换</button> </div> ); }
点击交换,但是计数器 count 没有被重置,因为 key 相同,节点类型 Counter 也相同。
解决办法:
{showFirst ? <Counter key="apple" label="苹果" /> : <Counter key="banana" label="香蕉" />}

React diff 算法在 key 变了时的行为

  • 销毁旧节点
    • 卸载对应的 DOM 元素或组件实例。即使标签/组件类型相同,也不会复用。调用组件的 componentWillUnmount / useEffect 的清理函数。
  • 创建新节点
    • 根据新的 key,重新挂载一个全新的 DOM 节点 / 组件实例,初始化组件的 state。
  • 替换子树
    • 旧子树被销毁,新子树从头创建。
  • 不会保留内部状态
    • DOM 内部状态会丢失(例如:input 的用户输入、checkbox 的勾选、video 的播放进度)。React 组件实例的 state 会被清空,重新初始化。
  • 不会保留引用关系
    • ref 会重新绑定到新节点 / 新组件。