默认情况下,当你修改 each
块的值时,它会在块的末尾添加和删除 DOM 节点,并更新任何已更改的值。但这可能不是你想要的效果。
用实例来说明比解释更容易理解。在 Thing.svelte
中,name
是一个动态属性,而 emoji
是一个常量。
多次点击”Remove first thing”按钮,观察发生了什么:
- 它移除了最后一个组件。
- 然后它更新了剩余 DOM 节点中的
name
值,但没有更新 emoji。
如果你有 React 学习背景,这可能看起来很奇怪,因为你习惯了在状态改变时整个组件重新渲染。Svelte 的工作方式不同:组件只”运行”一次,后续更新是”细粒度的”。这使得操作更快,并给你更多控制权。
解决这个问题的一种方法是将 emoji
设为 $derived
值。但与其删除最后一个组件并更新其他所有组件,不如直接删除第一个 <Thing>
组件更合理。
要实现这一点,我们需要为 each
块的每次迭代指定一个唯一的键:
App
{#each things as thing (thing.id)}
<Thing name={thing.name}/>
{/each}
你可以使用任何对象作为键,因为 Svelte 在内部使用
Map
—— 换句话说,你可以使用(thing)
而不是(thing.id)
。但是,使用字符串或数字通常更安全,因为这意味着即使在没有引用相等性的情况下(例如从 API 服务器更新数据时)身份也能保持不变。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
import Thing from './Thing.svelte';
let things = $state([
{ id: 1, name: 'apple' },
{ id: 2, name: 'banana' },
{ id: 3, name: 'carrot' },
{ id: 4, name: 'doughnut' },
{ id: 5, name: 'egg' }
]);
</script>
<button onclick={() => things.shift()}>
Remove first thing
</button>
{#each things as thing}
<Thing name={thing.name} />
{/each}