📜

Svelte Tutorial: Keyed each block を理解する

2022/08/18に公開

※ローカルな環境にメモしてた内容を転記

Svelte チュートリアルのリストでのループ( {#each} )の仕組みを理解するまで長かったので記録。

Svelte tutorial

何がわからなかったかというと「key 指定無しだとなぜ絵文字が更新されないか」。

結論としては「データリストのインデックスを key として扱い、同じ key が新旧データにあれば、旧データを新データで更新可能なプロパティだけ更新する」コードが生成されるから。生成されるのはしょうがないってやつですね、ユーザの使い方の問題だし。

さて、本ページタイトルと同名のチュートリアルでは、下記のようなデータを {#each} で表示し、ボタンにより先頭から1行ずつ消してくよっていうプログラムでデータの更新について学ぶ。

インデックス id name(exported) emoji
0 1 apple 🍎
1 2 banana 🍌
2 3 carrot 🥕
3 4 doughnut 🍩
4 5 egg 🥚

初期のソースコードだと先頭の apple 消すと、先頭の文にある name = banana になるのは良いんだけど emoji =🍎 になってるんですよね。ついでに DOM ノードの先頭ではなく末尾が消える。これは書いてある通り。

解決法は「key を指定して Svelte ちゃんに DOM ノードの特定にはこれ使うよ」って伝えること。まぁ key 渡してるんだしそんな気もするし、ちゃんとできてる。これで納得して次進めれば良かったんだが気になってしまった。

emoji ってなんで変わらないんだっけ?

実は言及されてなくて。同じ悩みの方が StackOverflow にいらっしゃったのでした。

Further explanation of Svelte's keyed each block

回答が有って質問者に称賛されていて良い感じ。しかし、英語は何となく読めるけど、知らん単語あると面倒なので DeepL 通して読んだ。でかいつまんで書く。

key を指定しない

リストのインデックスを key として利用する。同じ key が新旧データに有れば旧データを新データで更新する処理を実行するので、先頭削除した新データ[0]で旧データ[0] のうち export した name だけを更新する。要するに参照渡し使って別オブジェクト更新したのと同じことが起こさせてしまったわけです。ごめんね、Svelte ちゃん。

そして Svelte ちゃんは要らなくなった 5 つ目の DOM ノードをしっかり消してくれるコードを生成してくれているので、DOM ノードの末尾が消えるのです。新データのインデックス 0~3 しかないからね。

旧データ

インデックス(key) id name(exported) emoji
0 1 apple 🍎
1 2 banana 🍌
2 3 carrot 🥕
3 4 doughnut 🍩
4 5 egg 🥚

新データ

インデックス(key) id name(exported) emoji
0 2 banana 🍌
1 3 carrot 🥕
2 4 doughnut 🍩
3 5 egg 🥚

結果データ

インデックス(key) id name(exported) emoji
0 1 banana 🍎
1 2 carrot 🍌
2 3 doughnut 🥕
3 4 egg 🍩

key 指定をする

新旧データをちゃんと比較してくれて、先頭消えたから詰めて~みたいなこともしてくれる。よって想定通りになるというわけです。

旧データ

インデックス id(key) name(exported) emoji
0 1 apple 🍎
1 2 banana 🍌
2 3 carrot 🥕
3 4 doughnut 🍩
4 5 egg 🥚

新データ

インデックス id(key) name(exported) emoji
0 2 banana 🍌
1 3 carrot 🥕
2 4 doughnut 🍩
3 5 egg 🥚

結果データ

インデックス id(key) name(exported) emoji
0 1 banana 🍌
1 2 carrot 🥕
2 3 doughnut 🍩
3 4 egg 🥚

Discussion