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

correctness/each-index-key · Index used as each key

{#each} のキーに index を使うと、要素の同一性が位置で決まります。キーを付けないのと同じバグが、見えにくい形で起こります。

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

チェック内容

キーが index の束縛そのものになっている {#each} ブロック(例: {#each items as item, i (i)})を検出します。

index の単純な文字列化((String(i))、(Number(i))、(`${i}`)、(i.toString())、(i + ''))や、(i!) を含む TS ラップ形式も検出します。これらも位置ベースの同一性であることに変わりはありません。

検出しないもの:

  • 複合キー((item.id + '-' + i)、(`${item.id}-${i}`))。重複アイテムを含むリストでは素のアイテムキーが duplicate-key エラーを投げるため、index の付加が意図的な回避策になっている場合があります。ただしこのキーもアイテムが移動すると変わるため、移動したアイテムは追跡されず破棄・再作成されます。可能なら真に一意な id を使ってください。
  • 長さのみのプレースホルダーリスト({#each [...Array(n)] as _, i (i)} など)。アイテムの同一性を持ちません。
  • 定数リスト。インラインの配列リテラルと、コンポーネント自身のスクリプトにある const の配列リテラルのうちファイル内で変更される余地がないもの、そうしたリテラルをプロジェクト内のモジュールから import したもの(correctness/each-key を参照)です。並べ替えが起きないため、index が安定した同一性になります。import したリストを、それを import している別のモジュールが書き換える場合は確認しません。

なぜ重要か

Svelte 公式ガイダンスは明確です。キーはオブジェクトを一意に識別しなければならず、index を使ってはいけません。

index キーではアイテムの同一性が位置に従うため、並べ替え・挿入・削除で要素の状態(フォーカス、入力値、トランジション)がアイテムではなく位置に張り付きます。キーなしブロックとまったく同じ故障モードです。しかもキーが見えている分だけ安全そうに見え、レビューではなく本番で発覚しがちです。

修正方法

アイテムを一意に識別する値でキーを付けます。

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

モードによる違い

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

無効化

並べ替えも途中への挿入や削除も決して起きないと確実に言えるリストなら、<!-- svelte-vitals-disable-next-line correctness/each-index-key --> で個別に抑制するか、ルールを無効化してください。

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