7.React 中的 ⁠key 属性有什么作用? 来源:https://w0hog67yl81.feishu.cn/wiki/Gpi7wiE8giLHTzkF0AtcHwmvnZf 采集状态:已完成正文采集;正文滚动至底部,按块及代码行号采集 深入理解 React 中的 key:它为何如此重要?​ ​ 在 React 开发中,当我们渲染一个列表时,几乎总会看到控制台提示一个熟悉的警告:Each child in a list should have a unique "key" prop。​ ​ 许多初学者可能会通过 key={index} 快速“解决”这个警告,但这样做有时会埋下隐患。key 属性的意义远不止是消除一个警告,它与 React 的核心工作机制——协调(Reconciliation)算法紧密相关。​ ​ 理解 key 的真正作用,是深入掌握 React 性能优化和组件状态管理的关键一步。​ ​ key 的核心使命:元素的“身份证”​ ​ 我们可以将 key 想象成列表中每个元素的**唯一身份证**。​ ​ 当 React 渲染一个列表时,它会生成一个虚拟 DOM 树。当列表数据发生变化(例如,增、删、改、重排),React 需要高效地更新真实 DOM。它通过对比新旧两棵虚拟 DOM 树的差异,找出最小的变更集来执行更新,这个过程就是“协调”。​ ​ 如果没有 key,React 只能通过元素在列表中的**位置(index)**来逐个对比。我们来看一个场景:​ ​ 假设我们有一个简单的列表:​ ​ ​ 现在,我们在列表的开头插入一个新项目“橙子”:​ ​ ​ 在 React 的视角里,如果没有 key,它会这样对比:​ ​ 1. “咦,第一个
  • 的内容从‘苹果’变成了‘橙子’,我需要更新它。”​ 2. “第二个
  • 的内容从‘香蕉’变成了‘苹果’,我也需要更新它。”​ 3. “哦,最后多出来一个
  • 叫‘香蕉’,我需要新建并插入它。” 这显然不是最高效的方式。实际上,我们只是插入了一个元素,而原有的两个元素并没有改变。这种基于索引的对比,导致了大量不必要的 DOM 操作。​ ​ 现在,我们为列表加上 key:​ ​ // 初始状态 // 更新后 ​ 有了 key 这个“身份证”,React 的对比过程就变得智能多了:​ ​ 1. React 看到 key="orange" 是一个全新的元素,于是新建它。​ 2. React 在旧的列表中找到了 key="apple" 和 key="banana",发现它们只是位置移动了,内容没有变化。于是,React 不会重新创建它们,而是**高效地移动**现有的 DOM 节点到新的位置。 通过 key,React 能够准确识别出哪些元素是新增的、哪些是删除的、哪些只是移动了位置,从而最大限度地重用已有的 DOM 结构,大大提升了性能。​ ​ key 的三个基本原则​ ​ 一个合格的 key 需要满足以下三个条件:​ ​ 1. 唯一性(Unique):key 在兄弟节点之间必须是唯一的。一个列表中不能出现两个相同的 key。​ 2. 稳定性(Stable):key 的值必须是稳定且可预测的。同一个元素在多次渲染中应该保持相同的 key。我们不应该在渲染过程中使用 Math.random() 或其他不稳定的值来生成 key。​ 3. 可关联性(Associated):key 应该与列表中的数据项本身相关联。通常,我们使用数据项中的唯一标识符,如 item.id。 为什么不推荐使用 index 作为 key?​ ​ 使用数组索引 key={index} 是一个常见的“反模式”,尤其是在列表会发生变化的情况下。我们来看一个包含输入框的列表:​ ​ function ToDoList() { const [todos, setTodos] = useState([ { id: 1, text: '学习 React' }, { id: 2, text: '写一篇博客' }, ]); // ... 删除第一个 todo 的逻辑 return ( ); } ​ 假设我们在这个列表中,删除了第一项 学习 React。​ ​ • 期望行为:第一个
  • 连同其内部的输入框一起被移除。​ • 实际行为(当 key={index} 时)**: a. 删除前,列表是: ▪
  • (输入框值为 "学习 React") ▪
  • (输入框值为 "写一篇博客") b. 删除数据后,todos 数组变为 [{ id: 2, text: '写一篇博客' }]。React 重新渲染。 c. React 对比新旧列表: ▪ 新的
  • (对应 "写一篇博客") 与旧的
  • 对比。React 认为这是同一个组件,因此**复用**了旧的 DOM 节点,仅仅是将 defaultValue 更新为 "写一篇博客"。 ▪ 旧的
  • 被认为是多余的,因此被删除。 结果就是,我们看到的界面上,第一个输入框的内容没有变(如果它是非受控组件),或者状态出现混乱。这是因为 React 错误地复用了组件实例和它关联的内部状态。​ ​ 如果使用 key={todo.id},React 就能精确地知道 key={1} 的元素被删除了,而 key={2} 的元素依然存在,从而正确地执行 DOM 操作。​ ​ 那么,什么时候可以使用 index 作为 key?​ ​ 虽然不推荐,但在极少数情况下,使用 index 作为 key 是可以接受的,前提是列表同时满足以下所有条件:​ ​ 1. 列表和项目是**静态**的,不会进行计算,也不会改变。​ 2. 列表中的项目**没有稳定唯一的 ID**。​ 3. 列表**永远不会被重新排序或过滤**。 在这种严格的条件下,使用 index 不会引入状态管理问题。但在绝大多数动态场景中,我们都应该优先使用数据自身的唯一标识作为 key。​ ​ key 的另一个妙用:强制组件重新挂载​ ​ 除了在列表中使用,key 还有一个强大的用途:**当 key 的值发生变化时,React 会销毁旧的组件实例并创建一个全新的实例**。​ ​ 这意味着组件的 constructor 会重新执行,所有内部状态(state)都会被重置到初始值。​ ​ 这在某些场景下非常有用。例如,我们有一个用户资料页面组件 UserProfile,它通过 props 接收 userId 来获取并展示用户信息。​ ​ ​ 当用户从一个人的资料页切换到另一个人的资料页时,currentUser.id 会改变。如果我们希望每次切换用户时,UserProfile 组件都完全重置(例如,清空旧数据、重置加载状态、重新触发 useEffect),而不是在现有组件上更新,我们可以这样做:​ ​ ​ 通过将 userId 同时用作 key,我们向 React 传达了一个明确的信号:当 key(也就是 userId)改变时,这是一个完全不同的 UserProfile 实例,请丢弃旧的,创建一个新的。这是一种非常简洁而声明式地管理组件生命周期和状态的方式。​ ​ 总结​ ​ key 在 React 中扮演着至关重要的角色,它不仅仅是为了消除警告。​ ​ • 核心作用:key 是 React 在协调过程中识别元素的唯一标识,帮助 React 高效地增、删、改和重排元素。​ • 最佳实践:始终使用从数据中获取的**稳定且唯一**的标识(如 item.id)作为 key。​ • 避免使用 index**:在动态列表(可排序、过滤、增删)中,使用 index 作为 key 会导致性能问题和难以察觉的状态 bug。​ • 强制重载:改变一个组件的 key 可以强制该组件重新挂载,这是一种重置其内部状态的有效策略。 深入理解并正确使用 key,能帮助我们构建出更健壮、性能更优的 React 应用。从今天起,认真对待每一个 key 吧。​ ​