
教程【免费下载链接】jstipsThis is about useful JS tips!项目地址https://gitcode.com/gh_mirrors/js/jstips点击查看免费下载本篇是开源仓库 jstips 中「Keys in children components are important」一文的深度展开。文章围绕 React 中从数组动态渲染子组件时必须提供的key属性完整讲解其作为唯一且固定 ID的识别原理、正确用法、两大反模式数组索引与Math.random()、自定义唯一 ID 的生成策略以及与动画组件、性能优化的关系。读完你既能写出可复用、状态不串位的列表组件也能在代码评审中一眼识破 key 相关的地雷。一、key 是什么React 用来识别组件身份的固定 ID在 React 中当你用map()、Array.from()等方式从数组动态创建一组组件时必须为每一个被创建的组件传递一个key属性。key是一个唯一且固定unique and constant的 idReact 依靠它在 DOM 中识别每一个组件并判断两次渲染之间它到底是不是同一个组件。这正是 关联文档 的核心论断使用key可以确保子组件被**保留preserved**而不会被重复创建使用key可以防止奇怪的事件发生例如列表项的状态互相串位、输入框内容错乱、动画异常中断等。需要特别强调的一点是key 与性能的关系是间接的。React 核心团队成员 Paul OShannessy 曾明确指出Key 跟效能不太相关它跟元件識別較有關係反之它間接提升了效能。隨機賦值和改變數值將無法識別。也就是说key的本质是身份identity——让 React 能够稳定地认出同一个组件。身份稳定之后组件实例得以复用、DOM 免于无谓重建性能自然随之提升反过来如果key每次渲染都在变比如随机数React 就认不出这个组件只能销毁重建这才是性能灾难的根源。二、从 diff 原理看为什么认得出比渲染得快更根本要理解key为何如此重要需要回到 React 的**协调reconciliation**机制。React 在每次状态更新后会把新的元素树与上一次的虚拟 DOM 树做 diff 对比尽可能复用已有节点。对于同层级的列表节点React 使用一个经典的双指针算法进行比对如果没有keyReact 只能按位置逐一比对第 0 项对比第 0 项、第 1 项对比第 1 项……一旦列表头部插入或删除了元素所有后续项的位置全部平移React 会误以为它们是不同的组件导致整个列表的 DOM 重建如果提供了keyReact 可以按key 值直接配对——只要 key 不变React 就认定这是同一个组件从而完整保留其 DOM 节点与内部状态。举个具体的场景一个待办列表用户在顶部新增了一条待办。若用数组索引当 key原本的第 0 项会变成第 1 项React 会把key0的旧节点复用给新的第一条数据于是输入框里残留的旧文本、勾选状态、滚动位置全部串位。而使用数据自带的稳定 ID 作为 key 时React 能精确地把新旧元素一一对应其余项原封不动。这也解释了原文档中的第一条实操准则使用对象内存在的唯一值。item.id、item.slug这类数据持久层自带的标识符天然满足唯一且固定的要求是最理想的 key 来源。三、正确姿势在父组件定义 key而不是子组件原文档给出了两条黄金准则使用对象内存在的唯一值作为 key在父组件定义 key而不是子组件内部。配套的对比示例// badkey 定义在了子组件内部的元素上列表层面依然认不出组件 ... render() { div key{item.key}{item.name}/div } ... // goodkey 由父组件在渲染子组件列表时直接提供 MyComponent key{item.key}/理解这个 bad/good 对比的关键在于key必须出现在渲染列表的父组件所输出的最外层元素/组件上。React 的协调过程是在父组件返回的整棵元素树层面进行的如果父组件渲染MyComponent/列表时没有提供 key那么即使在MyComponent内部的根元素上写了 key对列表层面的身份识别也毫无帮助——因为 React 在父组件这一层就已经无法配对兄弟节点了。一个完整可运行的参考写法// 父组件列表由 TodoItem 构成key 取数据自带的唯一 id function TodoList({ todos }) { return ( ul {todos.map(todo ( TodoItem key{todo.id} todo{todo} / ))} /ul ); }关于key的作用域还有一个常见误区需要澄清key 只需要在兄弟节点之间保持唯一不需要全局唯一。也就是说两个不同的列表里可以各自出现key1互不影响React 只在同一父节点下的直接子节点之间做 key 匹配。另外key是 React 的保留属性它不会作为 prop 传给子组件。如果子组件内部确实需要拿到这个 id请另行通过普通 prop如id{todo.id}传递。四、两大反模式数组索引与 Math.random()原文档明确警告了两种会被key击穿的写法。4.1 使用数组索引是坏习惯// bad用数组下标当 key {todos.map((todo, index) ( TodoItem key{index} todo{todo} / ))}数组索引的问题是它不稳定只要列表发生增、删、排序索引就会整体漂移元素的身份随之错乱。在头部插入、尾部删除、过滤后重排等场景下组件会被错误复用出现状态串位、动画错位等奇怪的事情。因此原文档将其明确列为反模式——仅在列表绝对静态、永不增删排序时才可以勉强接受工程上应尽量避免。4.2random()无法使用// bad每次渲染都生成新 keyReact 永远认不出这个组件 MyComponent key{Math.random()}/Math.random()每次渲染都产生新值这意味着同一个组件在两次渲染之间身份必然改变。React 会认为这是一个全新的组件于是每次更新都销毁旧实例、重建新实例组件内部state、DOM 焦点、动画进度全部丢失性能也随之下滑。随机值不构成身份这是 Paul OShannessy 那段话最直接的应用场景。五、自建唯一 ID要快要稳定要提前算好如果数据对象本身没有合适的唯一字段原文档给出的建议是创建你自己的唯一 id并确保这个方法运行速度足够快然后将它附加到对象上。所谓附加到对象上指的是在数据进入列表渲染之前就为每个条目生成并持久化一个 id而不是在render()里现场计算。常见做法包括// 数据入库时生成并附加渲染时直接读取 let nextId 0; function createTodo(text) { return { id: nextId, text, done: false }; } // 或者使用 uuid 类库在创建数据时生成 // const todo { id: uuid(), text, done: false };注意足够快这一约束id 的生成应当发生在数据创建/获取阶段一次性的 O(1) 操作绝不能放在每次渲染都会执行的render()或.map()回调里反复计算。只要 id 在组件生命周期内保持不变React 就能稳定识别列表性能与状态完整性就都有保障。六、性能收益与动画组件的硬性要求原文档指出当子组件数量很多或子组件本身是重量级expensive组件时使用 key 能显著提升性能。原理正如前文所述——key 让 React 在列表 diff 时可以精确复用已有 DOM 与组件实例避免整段重建。对于上千行的表格、长列表、带复杂子树的卡片列表稳定 key 带来的收益尤其可观。这与仓库中另一篇 React 技巧 排查页面重复渲染的根源 一脉相承先找到不必要的渲染/重建再用正确的 key 等手段消除它。此外还有一条容易被忽略的硬性要求所有 ReactCSSTransitionGroupReact 动画插件 React Transition Group 的前身的子组件都必须提供key属性。原因是动画库需要借助 key 识别哪些元素进入、哪些元素退出从而正确播放进出场动画——没有 keyReactCSSTransitionGroup 无法跟踪元素的增删动画要么不播要么错乱。这条规则与列表渲染的 key 规则同源凡是 React 需要持续追踪同一实例的地方都必须有稳定且唯一的 key。七、在 jstips 中继续深入 React 主题本文所讨论的 key 只是 React 组件协作的基石之一。jstips 仓库的 react 分类目录 还收录了若干与之互补的技巧从 State 到 Prop 的映射reselect 记忆化讲解如何让mapStateToProps的派生计算具备记忆避免无谓重算——与 key 一样目标都是减少无效更新增强 React 组件的组合通过props.children与插槽式 prop 实现组件复用是如何组织可复用组件的姊妹篇排查页面重复渲染的原因提供在componentDidUpdate中追踪 props/state 变化的调试套路可用于验证 key 修复前后的渲染行为。这些文章与本文共同构成了 jstips 中关于React 渲染与复用的完整知识链。小结回到原文档的核心结论key是 React 从数组中动态创建子组件时必须传递的、唯一且固定的标识它的作用是让 React 在 diff 时识别出同一个组件从而保护子组件不被重复创建、防止状态与 DOM 串位的奇怪事件。实践时记住四句话用对象中自带的唯一值id、slug当 key在父组件渲染列表的最外层提供 key而不是在子组件内部写永远不要用数组索引当 key静态列表除外永远不要用Math.random()自建 id 要在数据创建时生成并附加到对象上保证快而稳定大量或重量级子组件、以及 ReactCSSTransitionGroup 的动画子项必须依赖稳定 key 才能获得正确的复用与动画行为。原文繁体中文见 _posts/zh_TW/react/2016-01-02-keys-in-children-components-are-important.md英文原版与简体中文版分别位于 _posts/en/react/2016-01-02-keys-in-children-components-are-important.md 与 _posts/zh_CN/react/2016-01-02-keys-in-children-components-are-important.md可直接对照阅读。赞分享教程【免费下载链接】jstipsThis is about useful JS tips!项目地址https://gitcode.com/gh_mirrors/js/jstips点击查看免费下载相关推荐jstips React 系列子组件 key 为何重要——从身份识别到渲染性能优化jstips React 系列子组件 key 为何重要——从身份识别到渲染性能优化 本文基于 jstips 仓库中 西班牙语版 React Tip 02 ht教程jstips React 篇子组件 key 为什么重要——动态列表渲染中的身份识别与性能优化jstips React 篇子组件 key 为什么重要——动态列表渲染中的身份识别与性能优化 在 React 中当你用 Array.prototype.ma教程ToastFish 完整指南用 Windows 通知栏背单词ToastFish 完整指南用 Windows 通知栏背单词 ToastFish 是一款开源的背单词软件它把单词卡片通过 Windows 系统通知推送到屏幕桌面应用教育上一篇VLOOK™ 插件完全攻略从基础排版到高级交互的完整教程下一篇如何快速上手DeepSeek-R1纯强化学习驱动的推理革命与开源生态影响创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考