コンテンツにスキップ
svelte-vitals
Esc
↑↓移動↵開く⌘Jプレビュー
このページの内容

correctness/each-key · キー付き each ブロック

動的なデータを回す {#each} にはキーを付けましょう。

重大度: warning · カテゴリ: correctness

チェック内容

キーのない {#each} ブロックを検出します。次の形は対象外とします。

  • 定数のインライン配列リテラル({#each [1, 2, 3] as n})。長さが固定で並べ替えが起きないため、キーを付けても意味がありません。
  • 同じリテラルをコンポーネント自身のスクリプトで名前付きにしたもの(const tabs = [ … ]、export なし)。ただしファイル内で変更される余地がない場合に限ります。つまり、ほかの参照がすべてメンバーの読み取り(tabs.length。(tabs as readonly string[]).includes(x) のような型アサーション越しも含みます)、{#each tabs …}、for (const t of tabs) のループ、新しい配列へのコピー([...tabs, extra])のいずれかである場合です。変更するメソッド呼び出し(tabs.push)、それを介した代入、tabs の受け渡しや、配列リテラル以外でのスプレッドがあれば検出対象のままです。
  • 同じリテラルをプロジェクト内のモジュールから import したもの(export const TABS = [ … ]。as const や satisfies 付きも含む)。$lib などの kit.alias、相対パス、バレルの再エクスポートを経由しても解決し(拡張子を省いた runes モジュール、たとえば state.svelte.ts を指す $lib/state.svelte も含む)、TABS としても import * as ns の ns.TABS としても回せます。モジュール内で書き換えられておらず(import したものかグローバルの関数・コンストラクタに渡す z.enum(TABS) や new Set(TABS) はそのモジュールでは読み取りとみなし、そのモジュール自身が宣言する関数に渡す場合は読み取りとみなしません。モジュール内の const の別名(const labels = custom ?? TABS)も同じ読み取りに限ります)、コンポーネント側も上記の読み取りだけである場合に限ります。パッケージや JSON ファイルからの import、配列リテラルではない export(LIST.map(…) やスプレッドを含むもの)は検出対象のままです。同じリストを import している別のモジュールからの書き換えは確認しません。
  • アイテムを取らない each({#each { length: 8 }, i} という「N 回描画する」パターン)。キーを付けられるようなアイテムの同一性が存在せず、唯一付けられる index 自体をキーにしても無意味です。
  • 長さのみのリスト(Array(n)、[...Array(n)]、Array.from({ length: n })、Array.from(new Array(n))、それらに .fill(x) や .keys() を続けたもの、as _ のようにアイテムを受け取る { length: n })。順序に意味のない固定長のプレースホルダー/スケルトンリストであり、キーを付けても意味がありません。

なぜ重要か

キーがないと、リストの並べ替えや要素の挿入、削除が起きたとき、Svelte は末尾でのノードの追加と削除で対応し、途中にある DOM ノードのデータを書き換えます。そのため要素の状態(フォーカス、入力、トランジション)がアイテムではなく位置に張り付いてしまい、余計な処理も発生します。キーがあれば、Svelte は正しいノードを挿入、移動、削除できます。

修正方法

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

モードによる違い

ありません。このルールが読むのは同じ .svelte / .ts のソースファイルなので、CLI、Vite プラグインのビルド、ライブダッシュボードの静的ベースラインのいずれでも結果は同一で、レンダリング済み HTML の解析で再評価されることもありません。--route で実行範囲を絞ると、このルールは動きません。コンポーネントスコープのルールには、検出を紐づけるルートが無いためです。

無効化

個別に抑制するには、対象行の直前に <!-- svelte-vitals-disable-next-line correctness/each-key --> を置きます。ルールごと無効化するには、次のように設定します。

export default {
  rules: {
    'correctness/each-key': 'off'
  }
};