专业编程基础技术教程

网站首页 > 基础教程 正文

React - Key属性的黑魔法以及它是如何提高性能的

ccvgpt 2024-08-07 18:56:02 基础教程 14 ℃

在React中,Key是如何工作的?我们需要弄清楚理论:“Key”是什么以及 React 为什么需要它。简言之,如果存在“key”属性,React 使用它作为在重新渲染期间识别相同类型元素的一种方式。换句话说,仅在重新渲染期间和相同类型的相邻元素(即平面列表)时才需要它(这个很重要!)。

重新渲染过程中的简化算法如下所示:

React - Key属性的黑魔法以及它是如何提高性能的

首先,React 会生成元素的“之前”和“之后”“快照”。
其次,它将尝试识别页面上已经存在的那些元素,以便可以直接使用而不是重新创建它们。

  • 如果“key”属性存在,它将假设具有相同“before”和“after”键的项目是相同的
  • 如果“key”属性不存在,它只会使用兄弟的索引作为默认的“key”

第三,它将:
- 删除“之前”阶段存在但“之后”阶段不存在的项目(即ummount它们)
- 重新创建“之前”不存在的项目(即mount它们)
- 更新“之前”存在并继续“之后”存在的项目(即重新渲染它们)

千万不要使用随机数作为Key

看下面有两个组件, CountryList会渲染每个Country。

现在Item上没有“key”属性。 那么当 CountryList 组件重新渲染时会发生什么?React 将看到那里没有“Key”,发现没有,这是React会使用CountryList数组的索引作为Key。而我们的数组没有改变,所以所有的Item都将被识别为“已经存在”,并且这些Item在被重新渲染时将被直接使用。从本质上讲,这与将 key={index} 显式添加到 Item 没有什么不同。简而言之:当 CountryList 组件重新渲染时,每个 Item 也会重新渲染。 如果我们将 Item 包装在 React.memo 中,我们甚至可以摆脱那些不必要的重新渲染并提高我们列表组件的性能。

那么如果我们把Key设成随机数会怎么样?

在这种情况下:在 CountryList 的每次重新渲染中,React 都会重新生成“关键”属性。由于存在“key”属性,React 将使用它作为识别“现有”元素的一种方式。由于所有“key”属性都是新的,所有“之前”的Item都将被视为“已移除”,每个Item都将被视为“新”,React 将unmount所有Item并重新mount它们。简而言之:当 CountryList 组件重新渲染时,每个 Item 都将被销毁并从头开始重新创建。

与我们谈论性能时的简单重新渲染相比,重新mount组件要昂贵得多。此外,在 React.memo 中包装项目的所有性能改进都将消失 - 记忆将不起作用,因为每次重新渲染都会重新创建项目。

你可以做一下测试,用两种情况去渲染一个大的列表,可以清楚的感受到这种方案的卡顿。

为什么也要避免使用index作为Key

现在应该很明显了,为什么我们需要稳定的“Key”属性,在重新渲染之间持续存在。 但是数组的“索引”呢? 即使在官方文档中,也不推荐使用它们,理由是它们可能会导致错误和性能影响。 但是,当我们使用“Index”而不是某些唯一 ID 时,究竟发生了什么会导致这样的后果?

首先,我们不会在上面的示例中看到任何这些。所有这些错误和性能影响只发生在“动态”列表中——列表中,项目的顺序或数量可以在重新渲染之间改变。 为了模仿这一点,让我们为我们的列表实现排序功能:

每次我单击按钮时,数组的顺序都会颠倒。 我将在两个变体中实现该列表,如下两图:

通过chrome开发者工具可以先给CPU限速,这样看得更明显。您可以看到基于“Index”的列表稍微慢一些。

为什么会这样?秘密当然是“Key”值:

React 生成元素的“之前”和“之后”列表,并尝试识别“相同”的Item。从 React 的角度来看,“相同”的Item是具有相同Key的Item。在基于“索引”的实现中,数组中的第一项将始终具有 key="0",第二项将具有 key="1" 等等 - 无论数组的排序如何

因此,当 React 进行比较时,当它在 "before" 和 "after" 列表中看到 key="0" 的项目时,它认为它是完全相同的Item,只是具有不同的 props 值,即country 值,在我们触发排序后发生了变化。因此它对同一个Item做了它应该做的事情:触发它的重新渲染周期。并且由于它认为 country 属性值发生了变化,它会绕过Memo功能,并触发实际项目的重新渲染。

基于 id 的行为是正确且高效的:项目被准确识别,每个项目都被记忆,因此没有组件被重新渲染。如果我们向 Item 组件引入一些状态,这种行为将特别明显。 例如,让我们在单击它时更改它的背景:

基于 id 的列表的行为完全符合预期。但是基于索引的列表现在的行为很有趣:如果我单击列表中的第一项,然后单击排序——无论排序如何,前三项都保持选中状态。而这就是上述行为的症状:例如数组中的第一项,React 认为 key="0" 的项在状态改变前后完全一样,所以它重用了同一个组件实例,保持状态不变(即这个项目的 isActive 设置为 true),并且只更新props值(从第一个国家到最后一个国家)。

为什么使用index作为Key有时是个好主意, 什么鬼?

在前面的部分之后,很容易说“只为“key”属性使用唯一的项目 id”,不是吗?在大多数情况下,这是真的,如果你一直使用 id,可能没有人会注意到或介意。但是当你有知识时,你就有了超能力。现在,既然我们知道 React 渲染列表时到底发生了什么,我们可以作弊并使用 index 而不是 id 更快地制作一些列表。

一个典型的场景:分页列表。您的列表中的项目数量有限,您单击一个按钮 - 您希望在相同大小的列表中显示相同类型的不同项目。如果您使用 key="id" 方法,那么每次更改页面时,您都会加载具有完全不同 id 的全新项目集。这意味着 React 将无法找到任何“现有”项目、卸载整个列表并安装全新的项目集。但!如果您使用 key="index" 方法,React 会认为新“页面”上的所有项目都已经存在,并且只会使用新数据更新这些项目,而实际组件会被挂载。如果项目组件很复杂,即使在相对较小的数据集上,这也会明显更快。

完全相同的情况将出现在各种动态列表式数据中,您将现有项目替换为新数据集,同时保留列表式外观:自动完成组件、类似 google 的搜索页面、分页表。只是需要注意在这些项目中引入状态:它们必须是无状态的,或者状态应该与Props同步。

总结:

永远不要在“key”属性中使用随机值:它会导致项目在每次渲染时重新安装。当然,除非你是故意的。

在“静态”列表中使用数组的索引作为“键”并没有什么坏处——那些项目编号和顺序保持不变的列表。

当列表可以重新排序或可以在随机位置添加项目时,使用项目唯一标识符(“id”)作为“Key”

您可以将数组的索引用作具有无状态项目的动态列表的“Key”,其中项目被替换为新项目——分页列表、搜索和自动完成结果等,这将提高列表的性能。

Tags:

最近发表
标签列表