👨‍💻

【React】keyPropsって結局なんやねん。

に公開

はじめに

Reactでリストをレンダリングしていると、こんな警告メッセージを見たことはありませんか?

Warning: Each child in a list should have a unique "key" prop.

この警告は、Reactの開発でよく遭遇するものです。今回は、このkeyが何なのか、なぜ必要なのか、そして正しい使い方について解説していきます。

この記事で学べること

  • keyの基本的な役割
  • keyがない場合に起こる問題
  • 正しいkeyの指定方法
  • よくある間違いとその回避方法

keyとは何か

keyは、Reactがリスト内の各要素を識別するための特別なプロパティです。配列をmapメソッドでレンダリングする際に使用します。

const items = ['りんご', 'バナナ', 'オレンジ'];

function FruitList() {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}

上記のコードでは、各<li>要素にkey={index}を指定しています。

なぜkeyが必要なのか

Reactの再レンダリングとリスト表示

Reactは、データが変更されたときに効率的に画面を更新します。しかし、リストの要素が変わったとき、Reactはどの要素が追加・削除・変更されたのかを判断する必要があります。

keyがない場合、Reactはリストの順序だけを頼りに要素を識別しようとします。これが予期しない動作やパフォーマンスの問題を引き起こすことがあります。

keyがない場合に起こる問題

具体的な例を見てみましょう。

function TodoList() {
  const [todos, setTodos] = useState([
    { text: '買い物に行く' },
    { text: '掃除をする' },
    { text: '勉強する' }
  ]);

  return (
    <ul>
      {todos.map((todo) => (
        <li>{todo.text}</li>  // keyがない
      ))}
    </ul>
  );
}

このリストの先頭に新しい項目を追加した場合、Reactは次のような処理を行います。

keyがない場合、Reactは既存の要素を再利用できず、不必要な再レンダリングが発生してしまいます。

Reactがkeyをどう使うのか

仮想DOMと差分検出アルゴリズム

Reactは仮想DOMという仕組みを使って、実際のDOMを効率的に更新します。リストが変更されたとき、Reactは次のような手順で処理を行います。

keyによる要素の識別メカニズム

keyがある場合、Reactは以下のように要素を識別します。

// 更新前
[
  { id: 1, text: 'タスクA' },
  { id: 2, text: 'タスクB' },
  { id: 3, text: 'タスクC' }
]

// 更新後(タスクBを削除)
[
  { id: 1, text: 'タスクA' },
  { id: 3, text: 'タスクC' }
]

keyとしてidを使用している場合、Reactは次のように判断します。

  • id: 1の要素は変更なし
  • id: 2の要素は削除された
  • id: 3の要素は変更なし

このため、id: 2の要素だけをDOMから削除すればよいと判断できるのです。

正しいkeyの指定方法

適切なkey値の選び方

keyには、以下の条件を満たす値を使用すべきです。

一意性: 兄弟要素の中で重複しない値
安定性: レンダリングごとに変わらない値
予測可能性: データと紐づいた値

ユニークなIDを使う

最も推奨される方法は、データに含まれるユニークなIDを使用することです。

function UserList({ users }) {
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>
          {user.name}
        </li>
      ))}
    </ul>
  );
}

// データの例
const users = [
  { id: 'user_001', name: '田中太郎' },
  { id: 'user_002', name: '佐藤花子' },
  { id: 'user_003', name: '鈴木次郎' }
];

データベースから取得したデータには通常、IDが含まれているので、それを使うのが最適ですね。

配列のインデックスを使う場合の注意点

インデックスをkeyとして使うことは可能ですが、いくつかの条件下でのみ推奨されます。

インデックスを使ってもよいケース

  • リストの項目が並び替えられない
  • リストの項目が追加・削除されない
  • リストが静的である
const staticItems = ['赤', '青', '黄色'];

function ColorList() {
  return (
    <ul>
      {staticItems.map((color, index) => (
        <li key={index}>{color}</li>
      ))}
    </ul>
  );
}

よくある間違いとアンチパターン

インデックスをkeyに使うべきでないケース

動的なリストでインデックスをkeyとして使うと、問題が発生します。

function DynamicTodoList() {
  const [todos, setTodos] = useState([
    '買い物',
    '掃除',
    '勉強'
  ]);

  const addTodo = () => {
    setTodos(['新しいタスク', ...todos]); // 先頭に追加
  };

  return (
    <div>
      <button onClick={addTodo}>タスク追加</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            <input type="checkbox" />
            {todo}
          </li>
        ))}
      </ul>
    </div>
  );
}

このコードの問題点を図で見てみましょう。

Reactはkeyが同じ要素を同一のものと判断するため、チェックボックスの状態が意図しない要素に引き継がれてしまいます。

ランダム値をkeyにする問題

毎回異なる値を生成してkeyにするのも避けるべきです。

// 悪い例
{items.map((item) => (
  <li key={Math.random()}>{item}</li>
))}

// 悪い例
{items.map((item) => (
  <li key={Date.now()}>{item}</li>
))}

ランダム値や現在時刻をkeyにすると、再レンダリングのたびに全ての要素が新しいものとして扱われ、パフォーマンスが大幅に低下します。

実際のバグ例

入力フォームを含むリストで、インデックスをkeyに使った場合の具体的なバグを見てみましょう。

function CommentList() {
  const [comments, setComments] = useState([
    { text: 'コメント1' },
    { text: 'コメント2' }
  ]);

  const deleteComment = (index) => {
    setComments(comments.filter((_, i) => i !== index));
  };

  return (
    <div>
      {comments.map((comment, index) => (
        <div key={index}>
          <input defaultValue={comment.text} />
          <button onClick={() => deleteComment(index)}>削除</button>
        </div>
      ))}
    </div>
  );
}

このコードで最初のコメントを削除すると、2番目のコメントのテキストが表示されますが、input要素の内部状態は1番目のものが残ってしまいます。

ベストプラクティス

安定したIDの使用

データにIDがない場合は、ライブラリを使って生成することもできます。

import { v4 as uuidv4 } from 'uuid';

function TodoApp() {
  const [todos, setTodos] = useState([]);

  const addTodo = (text) => {
    const newTodo = {
      id: uuidv4(), // ユニークなIDを生成
      text: text
    };
    setTodos([...todos, newTodo]);
  };

  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

key選択のチェックリスト

適切なkeyを選ぶ際は、以下のポイントをチェックしましょう。

判断基準

  1. データベースのIDがあれば、それを使う
  2. 複数の値を組み合わせて一意な文字列を作れる場合は、それを使う(例: ${user.email}-${index})
  3. リストが完全に静的なら、インデックスを使っても問題ない
  4. 動的なリストでIDがない場合は、UUID等で生成する

まとめ

keyはReactがリストの要素を効率的に管理するための重要な仕組みです。適切なkeyを指定することで、パフォーマンスの向上とバグの防止につながります。

重要なポイント

  • keyは兄弟要素の中で一意である必要がある
  • データに含まれるIDを使うのが最も推奨される
  • 動的なリストでインデックスをkeyに使うのは避ける
  • ランダム値や現在時刻をkeyにしてはいけない

これらのポイントを押さえて、正しくkeyを使っていきましょう。最初は意識する必要がありますが、慣れてくれば自然と適切なkeyを選べるようになりますよ。

Discussion