🔢

【React】安易にkeyにindexを指定するのはいい加減やめようの会

に公開

こんにちは!
株式会社Sally エンジニアの haruten です♪

私たち株式会社Sallyでは、マーダーミステリーをスマホやPCで遊べるアプリ「ウズ」や、マーダーミステリーを制作してウズ上で公開・プレイできるエディターツール「ウズスタジオ」などを開発・運営しています。
https://sally-inc.jp/

最近、Next.jsの開発現場でkeyの扱いを甘く見ていたせいで、思わぬバグに悩まされました。
今回は、そのトラブルの体験談も交えながらkeyの重要性についてまとめてみます。

初心者向けの内容ですが、自分も見落としていたポイントであり、同じミスを防ぐための自戒も込めた備忘録です。

1. 何が起こったか(失敗談)

結論から言うと、並べ替え可能なリストを作成する際、いつものクセでkeyにindexを指定していました。
すると、リスト内のアイテムを選択して開くはずのモーダルの内容が、なぜか別のアイテムの情報になってしまうというバグが発生しました。

最初は何が原因か特定できずに悩んでいたのですが、よく見直してみるとkeyに“index”を使っていたことが原因で、Reactの再レンダリングが正しく行われず、状態管理がズレていたのが発覚しました。

失敗となるコードの例(並べ替え部分は省略)

// 例:keyにindexを使ったことで起きるバグ
const items = [
  { id: 'a', name: 'リンゴ' },
  { id: 'b', name: 'バナナ' },
  { id: 'c', name: 'オレンジ' },
];

export default function ItemList({ items, onClick }) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index} onClick={() => onClick(item)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

この状態でリストを並べ替えると、クリックして開かれるモーダルの内容が意図したものと違ってしまうことがあります。

本来あるべき正しい例

// IDなどの一意な値を指定
export default function ItemList({ items, onClick }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id} onClick={() => onClick(item)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

実際のところ、並べ替えや削除などの動的な変更が発生しない静的なリストであればkeyにindexを指定しても大きな問題は起きにくいです。
要素の順序や構成が変化しなければ、Reactの描画に特に支障はありません。

そのため、日々の実装の9割程度ではindexをkeyに使って問題が生じることはほとんどなく、私自身もこれまでそのやり方で困ったことはありませんでした。

しかしこの方法が習慣化してしまうと、いざ並べ替えなどの動的なリストを扱う場面で思わぬバグに遭遇しやすくなります。
また、普段あまり意識せずに設定してしまう部分だからこそ原因の特定に余計な時間がかかってしまいがちです。

だからこそ、「keyには必ず一意な値(IDなど)を指定する」というルールを自分の中で徹底しておくのが最も安全だと、今回改めて痛感しました。

2. そもそもkeyの役割とは?

Reactにおけるkeyプロパティはリストレンダリングで非常に重要な役割を持っています。
Reactの基本的な項目ですが、蔑ろにしないため再度復習しておきます。

以下はReact公式Docからの抜粋です。
https://ja.react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key

  • 要素の識別子
    keyはReactがリストの各要素を一意に識別するための「目印」「ID」の役割を担う。

  • 差分検出(効率的な更新)
    どの要素が「追加・削除・並び替え」されたかを効率よく特定し、DOM更新の最小化・高速化を実現するためにkeyが使われる。

  • 最良のkeyの条件
    →兄弟要素内で一意であること
    →可能な限り不変な値を使うこと

keyはReactが要素を一意に識別するために不可欠なだけでなく、アプリのパフォーマンスや正確な動作にも大きく関わってくるというのがわかります。

インデックスをキーとして利用すると、微妙かつややこしいバグの原因となります。

indexのキー利用も公式Docで非推奨となってますね。

同様に、key={Math.random()} などとしてキーをその場で生成してはいけません。こうするとキーがレンダーごとに一切合致しなくなり、コンポーネントと DOM が毎回再作成されるようになります。

同じく、一意なキーをその場で生成することも非推奨としていますね。

つまり、Reactは「キーで明確に区別できるリスト」を前提に設計されているため、一意なキーを持たないリストはそもそも想定されていないと言えるでしょう。

それでも一意な値を持たないリストを作成したい場合は

  • 静的かつ変更の無いリストならindexをkeyにする
  • データ内で最も一意に近く、かつ変更の可能性がない項目を探してkeyにする(createdAtなど)
  • データ取得時に一時的なIDを付与

といった最終手段を使うしかありませんが、やはり本質的な解決策は「元データに安定した一意のID(識別子)を持たせること」です。

3. まとめ

今回の経験を通じて、改めてkeyの重要性を強く実感しました。
keyは単なる識別子ではなく、Reactの効率的な再レンダリングや状態管理の核となる重要要素です。

安易にindexを使わず、可能な限り安定した一意のKeyを使う意識が大切だと感じました。
私自身も今後はより注意してkeyを扱い、同じようなミスを防いでいきたいと思います。

基本的なことではありますが、改めて皆さんの理解を深めるきっかけとなれたら嬉しいです。

UZU テックブログ

Discussion