Reactのkeyとは?一覧表示で必要な理由を理解しよう

Reactのkeyとは?

Reactで配列をmap()して一覧表示するときは、それぞれの要素にkeyを指定します。

{items.map((item) => (
  <li key={item.id}>{item.name}</li>
))}

keyは、Reactが「どの要素がどのデータに対応しているのか」を判断するための目印です。

スポンサーリンク

まずはkeyを付けずに一覧表示してみよう

まず、keyを付けずに配列を一覧表示してみます。

const items = [
  { id: 101, name: "さくら" },
  { id: 102, name: "たろう" },
  { id: 103, name: "すみれ" },
];

function App() {
  return (
    <ul>
      {items.map((item) => (
        <li>{item.name}</li>
      ))}
    </ul>
  );
}

export default App;

画面には一覧が表示されますが、開発者ツールのConsoleを見ると、keyに関する警告が表示されます。

Reactは一覧を表示できないわけではありません。 ただし、それぞれの要素を正しく識別するために、keyを指定するよう求めています。

keyを付けてみよう

それぞれのデータが持っているidを、keyとして指定します。

const items = [
  { id: 101, name: "さくら" },
  { id: 102, name: "たろう" },
  { id: 103, name: "すみれ" },
];

function App() {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

export default App;

idは、それぞれのデータを区別するための値です。 Reactはこの値を使って、前回の表示と今回の表示で「同じ要素かどうか」を判断しやすくなります。

なぜkeyが必要なの?

一覧の内容がずっと同じなら、keyの必要性は分かりにくいかもしれません。 しかし、追加・削除・並び替えが起きると、keyが重要になります。

たとえば、次の3件がこの順番で表示されているとします。

  1. さくら
  2. たろう
  3. すみれ

ここで順番を「すみれ、さくら、たろう」に並び替えても、idはそれぞれのデータに付いたまま変わりません。

Reactはkeyを使うことで、順番が変わっても「どの要素がどのデータに対応しているのか」を正しく判別できます。

配列のindexをkeyにしてもいい?

map()では、配列のインデックスを表すindexを受け取ることもできます。

const items = [
  { id: 101, name: "さくら" },
  { id: 102, name: "たろう" },
  { id: 103, name: "すみれ" },
];

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

export default App;

この書き方でも警告は消えます。 ただし、追加・削除・並び替えがある一覧では、indexをkeyにするのは避けた方がよいです。

indexは「そのデータ自身の識別情報」ではなく、「配列のインデックス」を表す値だからです。

たとえば、「さくら、たろう、すみれ」の順に並んでいるとき、それぞれのindexは012です。 ここから「すみれ、さくら、たろう」の順に並び替えると、同じindexが別のデータに割り当てられます。

データ固有のIDをkeyにした場合の並び替え前後の比較 配列のindexをkeyにした場合の並び替え前後の比較
データ固有のIDは並び替えても変わりませんが、indexは並び順によって対応するデータが変わります。

たとえば各行に入力欄やチェック状態を持つコンポーネントがある場合、indexをkeyにしていると、 追加や並び替えのあとに入力内容やStateが意図しないデータと対応してしまうことがあります。

一方で、内容も順番も変わらない固定的な一覧では、indexを使っても問題にならない場合があります。 ただし、データに適切なidがあるなら、基本的にはそのidを使う方が分かりやすいです。

keyはその場で作らない

keyには、再レンダリングのたびに変わらない値を使う必要があります。

たとえば、次のようにmap()の中で毎回新しいIDを作るのは適切ではありません。

{items.map((item) => (
  <li key={crypto.randomUUID()}>{item.name}</li>
))}

この書き方では、再レンダリングされるたびにkeyが変わります。 Reactから見ると、前回とは別の要素として扱われやすくなってしまいます。

IDが必要なら、データを作成するときに一度だけ付けて、その値を使います。

import { useState } from "react";

function App() {
  const [items, setItems] = useState([]);

  const handleAdd = () => {
    const newItem = {
      id: crypto.randomUUID(),
      name: "新しいアイテム",
    };

    setItems((prevItems) => [...prevItems, newItem]);
  };

  return (
    <div>
      <button onClick={handleAdd}>追加</button>

      <ul>
        {items.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

この例では、アイテムを追加するときにidを作成し、その後は同じidを使い続けています。

keyは同じ一覧の中で重複させない

keyは、同じ一覧の中で重複しない値を指定します。

たとえば、次のように同じ一覧の要素へ同じkeyを指定するのはNGです。

<ul>
  <li key="1">さくら</li>
  <li key="1">たろう</li>
</ul>

2つのliに同じkeyが指定されているため、Reactがそれぞれの要素を正しく識別できません。

一方、別の一覧であれば、同じkeyを使うことができます。

<ul>
  <li key="1">さくら</li>
</ul>

<ul>
  <li key="1">たろう</li>
</ul>

この場合は別々の一覧なので、どちらにkey="1"があっても問題ありません。 keyはアプリ全体で一意にする必要はありません。

Fragmentにkeyを付ける場合

ここまでは、map()からliなどの要素を1つ返し、その要素にkeyを指定してきました。

1件のデータにつき複数の要素を返したい場合は、Fragmentでまとめることがあります。 この場合は、map()から直接返すFragmentにkeyを指定します。

import { Fragment } from "react";

const items = [
  { id: 101, name: "さくら" },
  { id: 102, name: "たろう" },
  { id: 103, name: "すみれ" },
];

function App() {
  return (
    <dl>
      {items.map((item) => (
        <Fragment key={item.id}>
          <dt>{item.name}</dt>
          <dd>学習中</dd>
        </Fragment>
      ))}
    </dl>
  );
}

export default App;

Fragmentの短縮記法<>...</>にはkeyを指定できません。 keyが必要な場合は、<Fragment key={...}>の形で記述します。

Fragmentの役割や短縮記法との違いについては、関連記事で詳しく解説します。

keyはpropsとして受け取れない

keyはReactが内部で使う特別な値なので、通常のpropsとは少し扱いが違います。

たとえば、次のようにkeyを指定しても、子コンポーネントでkeyをpropsとして受け取ることはできません。

function ItemRow({ item }) {
  return <li>{item.name}</li>;
}

function App() {
  const items = [
    { id: 101, name: "さくら" },
    { id: 102, name: "たろう" },
    { id: 103, name: "すみれ" },
  ];

  return (
    <ul>
      {items.map((item) => (
        <ItemRow key={item.id} item={item} />
      ))}
    </ul>
  );
}

export default App;

子コンポーネントでもIDを使いたい場合は、keyとは別にpropsとして渡します。

<ItemRow
  key={item.id}
  itemId={item.id}
  item={item}
/>

よくある勘違い

keyは画面に表示されるID?

いいえ。 keyはReactが一覧の要素を識別するために使う値で、指定しただけでは画面には表示されません。

keyは0、1、2と連番なら何でもいい?

一覧の追加・削除・並び替えがある場合は、配列のインデックスをそのままkeyにするのは避けた方がよいです。 データ自身を識別できるidなどを使いましょう。

keyは毎回違う値の方が安全?

逆です。 同じデータには、再レンダリング後も同じkeyが使われる必要があります。 keyは安定していることが大切です。

keyはアプリ全体で一意でないといけない?

そこまでの一意性は必要ありません。 同じ一覧の中で重複しなければ大丈夫です。

まとめ

Reactのkeyは、一覧の各要素をReactが識別するための値です。

  • map()で一覧表示するときは、それぞれの要素にkeyを指定する
  • keyには、データを一意に識別できる安定した値を使う
  • 追加・削除・並び替えがある一覧では、配列のindexをkeyにするのは避ける
  • 再レンダリングのたびに変わる値をkeyにしない
  • keyは同じ一覧の中で重複させない
  • keyは通常のpropsとして子コンポーネントへ渡されない

「警告を消すためにkeyを書く」のではなく、「同じデータをReactが見分け続けられるようにkeyを書く」と考えるのがポイントです。

スポンサーリンク