React 键的意义——视觉解释
免责声明:这是一个过于简化的说法,应该作为和解如何运作的基本指南,而不是一个完美的例子!
在 React 中处理数组时,每个元素的“key”属性的使用对于避免不必要的重新渲染性能损失至关重要。本文将解释为什么你应该始终清晰地定义键,以及如果不这样做会错过什么。
让我们从一个数组开始
const joshs = [{ Name: "Josh", }, { Name: "Joshina", }, { Name: "Notjosh", }]
React 组件的业务端,用于渲染上述数组
<div>
{ joshs.map((person, index) => ( <span key={index}>{person.name}</span>)) }
</div>
以及它输出的 HTML
<div>
<span key=’0’>Josh</span>
<span key=’1’>Joshina</span>
<span key=’3’>Notjosh</span>
</div>
效果很好!
...但!
新的乔希已经到来,他告诉其他人让开。
[{ Name: "Mega Josh"}, { Name: "Josh", }, { Name: "Joshina", }, { Name: "Notjosh", }]
我们的组件接收新列表,执行其操作......
<div>
{ joshs.map((person, index) => ( <span key={index}>{person.name}</span>)) }
</div>
并像以前一样将其打印出来。
<div>
<span key=’0’>Mega Josh</span>
<span key=’1’>Josh</span>
<span key=’2’>Joshina</span>
<span key=’3’>Notjosh</span>
</div>
效果很好!
...但!
让我们以非常简单的方式,深入了解一下 React 在呈现其新列表时所经历的过程。
React 组件,当你将其简化为最原始的形式(即从 JSX 转换而来)时,只是一个带有一组属性的对象。这些属性定义了它的类型、名称、状态、它接收的 props、是否有子组件等等。
每次数组发生变化时,<span>
都会创建一个新的 Josh 组件对象列表。React协调器会将新创建的对象与 DOM 中的当前版本进行比较。如果检测到某些属性之间存在任何差异,它会重新绘制组件,并认为它们是同一个对象,只是属性发生了变化。
因此,在我们的示例中,我们有原始数组(组件),可以大致翻译成如下内容......
[{
Type: "span",
Key: "0",
Children: "Josh"
}, {
Type: "span",
Key: "1",
Children: "Joshina"
}, {
Type: "span",
Key: "2",
Children: "Notjosh"
}]
协调器将查看键和组件属性(在我们的简化情况下为内容或子项),然后查看其先前的组件列表,以查看它是否与任何先前的组合匹配。
由于我们的列表使用数组索引位置作为其键,当“Mega Josh”到达并将所有组件向下移动一个位置时,由于 React 注意到键与它们之前的属性不匹配,每个比较现在都会失败!
[{
Type: "span",
Key: "0", // Expected 0 to match 'Josh'
Children: "Mega Josh" // IM DIFFERENT, REDRAW ME
}, {
Type: "span",
Key: "1", // Expected 1 to match 'Joshina'
Children: "Josh" // IM DIFFERENT, REDRAW ME
}, {
Type: "span",
Key: "2", // Expected 2 to match 'Notjosh'
Children: "Joshina" // IM DIFFERENT, REDRAW ME
}, {
Type: "span",
Key: "3", // IM NEW
Children: "Notjosh" // DRAW ME
}]
但是!我们可以避免这种情况。如果我们明确定义一个静态的、唯一的、并且与其关联的属性唯一关联的键,那么即使它的位置发生了变化,React 也可以识别出它是同一个组件。
让我们用唯一的键重建我们的组件
注意:在本例中,我使用name
属性来保持 josh 对象的简洁,但这并非最佳实践,因为两个组件拥有相同键的可能性相当高。尽可能使用数据对象中的某种主键。
<div>
{ people.map((person, index) => ( <span key={`key-${person.name}`}>{person.name}</span>)) }
</div>
导出的 HTML 现在看起来像
<div>
<span key=’key-Josh’>Josh</span>
<span key=’key-Joshina’>Joshina</span>
<span key=’key-Notjosh’>Notjosh</span>
</div>
以及更新后的数组 HTML
<div>
<span key='key-Mega Josh'>Josh</span>
<span key=’key-Josh’>Josh</span>
<span key=’key-Joshina’>Joshina</span>
<span key=’key-Notjosh’>Notjosh</span>
</div>
现在,键对于它们的数据对象(而不是它们的数组位置)来说是唯一的,因此当我们进行对象比较时
[{
Type: "span",
Key: "key-Josh",
Children: "Josh"
}, {
Type: "span",
Key: "key-Joshina",
Children: "Joshina"
}, {
Type: "span",
Key: "key-Notjosh",
Children: "Notjosh"
}]
协调器会发现有些组件并没有改变,只是移动了位置。新的组件会被创建并插入到列表的最前面,而不会影响到后面的组件。太棒了!
[{
Type: "span",
Key: "key-Mega Josh", // IM NEW
Children: "Mega Josh" // DRAW ME
}, {
Type: "span",
Key: "key-Josh", // Expected 'key-Josh' to match 'Josh'
Children: "Josh" // IM THE SAME, DONT REDRAW ME
}, {
Type: "span",
Key: "key-Joshina", // Expected 'key-Joshina' to match 'Joshina'
Children: "Joshina" // IM THE SAME, DONT REDRAW ME
}, {
Type: "span",
Key: "key-Notjosh", // Expected 'key-Notjosh' to match 'Notjosh'
Children: "Notjosh" // IM THE SAME, DONT REDRAW ME
}]
对于某些用途,性能影响可能很小(如果数组顺序不变,甚至可能不存在)。添加键的好处在经过排序/重新排序的大型数组中尤为明显,因为它可以消除大部分列表的渲染需求。真是太神奇了!
鏂囩珷鏉ユ簮锛�https://dev.to/jtonzing/the-significance-of-react-keys---a-visual-explanation--56l7