Svelte 的 keyed each block 的进一步解释

Further explanation of Svelte's keyed each block

我不明白教程中的这一部分:https://svelte.dev/tutorial/keyed-each-blocks

我可以看到 things 数组已正确更新,因此正确的 thing.color 已按预期传递。但是根据第一句话“默认情况下,当您修改 each 块的值时,它将在块的末尾添加和删除项目,并更新任何已更改的值。”,似乎是说 Svelte 在单击按钮时无论如何都会删除最后一个块,那么剩下的 4 个块将面临切片 things,即

[{ id: 2, color: '#6a00a8' },
 { id: 3, color: '#b12a90' },
 { id: 4, color: '#e16462' },
 { id: 5, color: '#fca636' }]

并且由于initial被声明为const,它不能再被更新,所以thing.id 1--4的颜色仍然存在。

这样理解正确吗?这种默认行为是否假设 each 块是可交换的?

然后它说使用 thing.id 作为 each 块的密钥将解决问题,即 {#each things as thing (thing.id)}。我不明白 each 块中的键是如何使用的,如果没有提供 thing.id ,默认键是什么。以及为什么在提供 thing.id 时默认密钥(如果有的话,或者默认无密钥)不起作用。

感谢您的澄清。

我相信当您不提供密钥时,它使用类似项目索引的东西作为默认值。这可以通过使用

来验证
{#each things as thing, index (index)}
    <Thing current={thing.color}/>
{/each}

这与不使用密钥的行为相同。


我们将为 id: 1 渲染的 <Thing> 称为 Thing1,依此类推。

没有提供密钥

当我们从列表中删除第一项时,Thing1 仍然保持不变,因为与其关联的键(在本例中为索引)保持不变。之前发送到 Thing2 的道具现在发送到 Thing1。这种情况一直发生在链条的下游。但是现在少了一个元素, Thing5 从 DOM.

中删除了

与键“0”(Thing1) 关联的组件 Thing 的实例在删除第一项时不会被销毁。发生这种情况是因为键保持不变(新数组在索引 0 处也有一个项目)。只有发送到 Thing1 的道具发生了变化,initial 变量保留了带有 id: 1.

的原始项目的颜色

提供 (thing.id) 密钥

id: 1的东西被移除时,不存在映射到“1”的Thing的任何实例。因此,Thing1 从 DOM 中删除。


另一种理解方式是,当您提供密钥时,您实际上是在告诉 Svelte 将每个渲染块映射到该密钥。当该密钥不再存在时,摆脱该块并将其从 DOM 中删除。但是如果密钥保持不变而道具发生变化,请更新道具而不是重新创建块。

当您不指定键时,它使用列表的索引作为键。因此,如果您从列表中删除项目,它不会重新创建或重新排序块,它只会更新道具。

API docs 解释如下:

If a key expression is provided — which must uniquely identify each list item — Svelte will use it to diff the list when data changes, rather than adding or removing items at the end. The key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change.

{#each items as item (item.id)}
  <li>{item.name} x {item.qty}</li>
{/each}

<!-- or with additional index value -->
{#each items as item, i (item.id)}
  <li>{i + 1}: {item.name} x {item.qty}</li>
{/each}

我发现这在教程中也缺乏适当的解释。但是,我认为文档更清楚——可以为每个函数提供一个唯一的键,以便每个迭代都被唯一标识。因此,当一个特定的元素从 提供给每个函数的数据,可以识别和删除正确的迭代。

有同样的疑问,然后我意识到“它将在块的末尾添加和删除项目”可能意味着这里可能适用于 DOM,所以即使你删除 first JavaScript 中的数组项,Svelte 总是删除 last DOM 节点。提供密钥后,DOM 和 JavaScript 可以执行相同的操作。

我也在为这个例子苦恼,为什么图标没有改变。我注意到初始化后在组件中计算的值 (const emoji = emojis[name]) 保持不变,但如果我们使用响应式声明:$: emoji = emojis[name]; 值将被重新计算并且这个示例工作正常。