- DAY 24:keyを理解する 前半 ― 「Reactがリストをどう見ているか」を知る
- keyとは何か
- なぜkeyが必要なのか
- IDをkeyに使う ― 一番安全で基本的な方法
- indexをkeyにする問題
- 実践:ユーザー一覧を作成しながらkeyを意識する
- indexをkeyにした場合との違いをイメージする
- 練習用テンプレート:ユーザー一覧+追加・削除
- DAY 24 前半のまとめ
- DAY 24:keyを理解する 中盤 ― 「動くUI」とkeyの関係を体で理解する
- Reactは「前回」と「今回」を比べている
- keyとは「ReactにとってのID」である
- indexをkeyにしたときに起きる「ズレ」をイメージする
- IDをkeyにすることで「ズレ」を防ぐ
- 実践:ユーザー一覧でkeyの選び方を意識する
- 練習用テンプレート:ユーザー一覧+メモ入力付き
- DAY 24 中盤のまとめ
- DAY 24:keyを理解する 後半 ― 「実務レベルで通用するkeyの感覚」を仕上げる
- keyとは何かを「一言で説明できる」ようにする
- なぜkeyが必要かを「差分更新」の視点で整理する
- IDを使用する ― 「データのID」と「Reactのkey」を揃える
- indexをkeyにする問題を「実務的な違和感」として捉える
- 実践:ユーザー一覧を作成しながらkeyの設計を仕上げる
- DAY 24 後半のまとめ
DAY 24:keyを理解する 前半 ― 「Reactがリストをどう見ているか」を知る
DAY 24では、Reactのリスト表示で必ず登場する key を深く理解していきます。 ここは、mapによるリスト表示を「安全に」「正しく」動かすための、とても重要なポイントです。
前半では、次のテーマを軸に進めます。
- keyとは
- なぜ必要か
- IDを使用する
- indexをkeyにする問題
- 実践:ユーザー一覧を作成
単に「警告を消すためのもの」として覚えるのではなく、 「Reactがリストをどう認識しているか」を理解するための入り口として、丁寧にかみ砕いていきます。
keyとは何か
「Reactがリストの各要素を識別するためのラベル」です
まず、key の正体を一言で言うと、
「Reactが、リストの各要素を識別するために使うラベル(IDのようなもの)」
です。
mapでリストを表示するとき、よく次のようなコードを書きます。
function UserList() {
const users = [
{ id: 1, name: "太郎" },
{ id: 2, name: "花子" },
{ id: 3, name: "健" },
];
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
JSXここでの key={user.id} が、まさに「Reactが各要素を識別するためのラベル」です。
Reactは、リストが更新されたときに、
- どの要素が追加されたか
- どの要素が削除されたか
- どの要素が並び替えられたか
を判断する必要があります。 そのときに使われるのが key です。
なぜkeyが必要なのか
「リストが変化したときに、正しく差分を取るため」です
Reactは、画面を毎回ゼロから描き直しているわけではありません。 「前回の描画」と「今回の描画」の差分を計算して、必要な部分だけ更新する」という仕組みで動いています。
リスト表示の場合、この「差分計算」が少し難しくなります。
例えば、次のようなリストがあるとします。
const users = [
{ id: 1, name: "太郎" },
{ id: 2, name: "花子" },
{ id: 3, name: "健" },
];
JSXここから「花子」を削除して、
const users = [
{ id: 1, name: "太郎" },
{ id: 3, name: "健" },
];
JSXになったとき、Reactはこう考えます。
- 「太郎」は前回も今回もいる
- 「花子」はいなくなった
- 「健」は前回も今回もいる
この「誰が残っていて、誰がいなくなったか」を判断するために、 key が必要になります。
もし key がなければ、Reactは「どの要素がどのデータに対応しているか」を正しく追跡できません。
IDをkeyに使う ― 一番安全で基本的な方法
「そのデータを一意に識別できる値」をkeyにする
最も基本で安全な方法は、
「そのデータを一意に識別できるIDをkeyにする」
ことです。
ユーザー一覧なら、よく次のような形になります。
function UserList() {
const users = [
{ id: 1, name: "太郎", email: "taro@example.com" },
{ id: 2, name: "花子", email: "hanako@example.com" },
{ id: 3, name: "健", email: "ken@example.com" },
];
return (
<div>
<h2>ユーザー一覧</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
<p>名前:{user.name}</p>
<p>メール:{user.email}</p>
</li>
))}
</ul>
</div>
);
}
JSXここでの user.id は、
- データベースやAPIから渡される「ユーザーID」
- 各ユーザーごとに一意な値
という前提で使われています。
重要ポイント:
- keyには「その要素を一意に識別できる値」を使うのが基本です。
- オブジェクト配列なら、
idフィールドをkeyにするのが最もよくあるパターンです。
indexをkeyにする問題
一見動くが、後で痛い目を見るパターンです
mapを使うとき、次のような書き方をしてしまうことがあります。
{users.map((user, index) => (
<li key={index}>{user.name}</li>
))}
JSX一見すると動きますし、Reactの警告も消えます。 しかし、これは推奨されない書き方です。
理由は、
「リストの並び替えや削除・追加が行われたときに、indexが変わってしまうから」
です。
具体的なイメージ
例えば、次のようなリストを考えます。
const users = [
{ id: 1, name: "太郎" }, // index 0
{ id: 2, name: "花子" }, // index 1
{ id: 3, name: "健" }, // index 2
];
JSXここから「太郎」を削除すると、
const users = [
{ id: 2, name: "花子" }, // index 0
{ id: 3, name: "健" }, // index 1
];
JSXという状態になります。
indexをkeyにしている場合、
- 「花子」は前回 index 1 → 今回 index 0
- 「健」は前回 index 2 → 今回 index 1
というように、同じユーザーなのに、keyが変わってしまいます。
Reactから見ると、
- 「index 0の要素が新しく現れた」
- 「index 1の要素も新しく現れた」
というように見えてしまい、 本来は「太郎だけが消えた」状態なのに、 「全部入れ替わった」ように誤解される可能性があります。
indexをkeyにすると何が起きるか
特に次のような場合に問題が顕在化します。
- 入力フォームを含むリスト
- チェックボックスやラジオボタンを含むリスト
- アニメーションやトランジションを含むリスト
indexをkeyにしていると、
- 入力内容が別の行に移ってしまう
- チェック状態が意図しない行に残ってしまう
- アニメーションが不自然に再生される
といった「気持ち悪い挙動」が起きることがあります。
重要ポイント:
- indexは「配列の並び」に依存する値であり、 並び替えや削除・追加で簡単に変わってしまいます。
- そのため、「その要素を一意に識別するID」としては不適切です。
実践:ユーザー一覧を作成しながらkeyを意識する
基本形:IDをkeyに使ったユーザー一覧
まずは、IDをkeyに使った正しい形のユーザー一覧を作ってみます。
import { useState } from "react";
function UserList() {
const [users, setUsers] = useState([
{ id: 1, name: "太郎", email: "taro@example.com" },
{ id: 2, name: "花子", email: "hanako@example.com" },
{ id: 3, name: "健", email: "ken@example.com" },
]);
const handleDelete = (id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
};
return (
<div>
<h2>ユーザー一覧</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
<p>名前:{user.name}</p>
<p>メール:{user.email}</p>
<button onClick={() => handleDelete(user.id)}>削除</button>
</li>
))}
</ul>
</div>
);
}
export default UserList;
JSXこのコードで確認できること
usersは「ユーザー情報のオブジェクト」が入った配列です。map()で各ユーザーをJSXに変換しています。key={user.id}で、各ユーザーを一意に識別しています。- 削除ボタンを押すと、
setUsersで配列を更新し、- Reactが
keyを使って「どのユーザーが消えたか」を正しく判断します。
- Reactが
ここでの大事な感覚:
- 「ユーザーID」と「画面上の要素」が、
keyを通して1対1で結びついている - 削除や追加があっても、IDが変わらない限り、そのユーザーに対応する要素は正しく追跡される
indexをkeyにした場合との違いをイメージする
indexをkeyにしたユーザー一覧(推奨されない例)
あえて、indexをkeyにした場合のコードも見ておきます。
import { useState } from "react";
function UserListBad() {
const [users, setUsers] = useState([
{ id: 1, name: "太郎", email: "taro@example.com" },
{ id: 2, name: "花子", email: "hanako@example.com" },
{ id: 3, name: "健", email: "ken@example.com" },
]);
const handleDelete = (id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
};
return (
<div>
<h2>ユーザー一覧(indexをkeyにした例)</h2>
<ul>
{users.map((user, index) => (
<li key={index}>
<p>名前:{user.name}</p>
<p>メール:{user.email}</p>
<button onClick={() => handleDelete(user.id)}>削除</button>
</li>
))}
</ul>
</div>
);
}
export default UserListBad;
JSXこのコードは一見動きますが、
- ユーザーを削除したときに、
- 残ったユーザーのindexが変わってしまう
- Reactから見ると「別の要素」として扱われる可能性がある
という問題を抱えています。
特に、各ユーザー行に入力フォームやチェックボックスを追加した場合、 「入力内容やチェック状態が別の行に移ってしまう」ような挙動が起きることがあります。
重要ポイント:
- indexをkeyにすると、「見た目は動いているように見えるが、内部的には不安定な状態」になりがちです。
- 実際のアプリでは、こうした不安定さがユーザー体験の違和感として現れます。
練習用テンプレート:ユーザー一覧+追加・削除
最後に、DAY 24前半の内容をまとめた練習用テンプレートとして、 「ユーザー一覧+追加・削除」ができるコンポーネントを示します。
import { useState } from "react";
function UserList() {
const [users, setUsers] = useState([
{ id: 1, name: "太郎", email: "taro@example.com" },
{ id: 2, name: "花子", email: "hanako@example.com" },
]);
const [nextId, setNextId] = useState(3);
const handleAddUser = () => {
const newUser = {
id: nextId,
name: `ユーザー${nextId}`,
email: `user${nextId}@example.com`,
};
setUsers((prevUsers) => [...prevUsers, newUser]);
setNextId((prevId) => prevId + 1);
};
const handleDeleteUser = (id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
};
return (
<div>
<h2>ユーザー一覧(追加・削除付き)</h2>
<button onClick={handleAddUser}>ユーザーを追加</button>
<ul style={{ marginTop: "8px" }}>
{users.map((user) => (
<li key={user.id}>
<p>名前:{user.name}</p>
<p>メール:{user.email}</p>
<button onClick={() => handleDeleteUser(user.id)}>削除</button>
</li>
))}
</ul>
</div>
);
}
export default UserList;
JSXこのテンプレートの中には、
- オブジェクト配列としてのユーザー一覧
map()によるリスト表示- JSXでの表示構造
key={user.id}による一意な識別- Stateによる追加・削除の管理
が自然に含まれています。
DAY 24 前半のまとめ
DAY 24の前半では、
keyが「Reactがリストの各要素を識別するためのラベル」であり、差分計算のために必須であること- リストが更新されたときに、「誰が残っていて、誰がいなくなったか」を判断するために、keyが使われること
- 一意なID(
idフィールドなど)をkeyにするのが最も安全で基本的な方法であること - indexをkeyにすることが、並び替え・削除・追加の場面で問題を生む理由を具体的にイメージしたこと
- ユーザー一覧の実装を通して、
map()+オブジェクト配列+JSX+key+State更新が1本の流れとしてつながる感覚を確認したこと
が大きなポイントになります。
この段階まで来ると、
- 「keyは警告を消すためのもの」ではなく、
- 「Reactがリストを正しく理解するための重要な仕組み」
として捉えられるようになっているはずです。 中盤以降では、さらに複雑なリストやフォーム付きのリストを題材に、 keyの重要性をより実践的な形で深めていきます。
DAY 24:keyを理解する 中盤 ― 「動くUI」とkeyの関係を体で理解する
DAY 24の中盤では、前半で学んだ keyの役割 を、 もう一歩「動くUI」の視点から深く理解していきます。
ここからのゴールは、
- 「なぜkeyが必要なのか」を、実際の挙動からイメージできるようになること
- 「IDをkeyにするべき理由」と「indexをkeyにする危険性」を、感覚として掴むこと
- ユーザー一覧を題材に、keyを意識したリスト設計ができるようになること
です。
Reactは「前回」と「今回」を比べている
差分更新という考え方を押さえる
まず、keyの話を深めるために、 Reactが画面をどう更新しているかをイメージしておきます。
Reactは、毎回ゼロからDOMを作り直しているわけではなく、
「前回の仮想DOM」と「今回の仮想DOM」を比べて、差分だけを実際のDOMに反映する」
という仕組みで動いています。
リスト表示の場合、この「差分を取る」処理が少し難しくなります。
例えば、ユーザー一覧が次のような状態だとします。
const usersBefore = [
{ id: 1, name: "太郎" },
{ id: 2, name: "花子" },
{ id: 3, name: "健" },
];
JSX削除や追加が行われて、次のようになったとします。
const usersAfter = [
{ id: 1, name: "太郎" },
{ id: 3, name: "健" },
{ id: 4, name: "新しいユーザー" },
];
JSXReactはここで、
- 「太郎」は前回も今回もいる
- 「花子」はいなくなった
- 「健」は前回も今回もいる
- 「新しいユーザー」が追加された
ということを判断したいわけです。
このときに使われるのが key です。
keyとは「ReactにとってのID」である
データのIDと、ReactのIDを一致させる
ユーザー一覧の例で考えると、 各ユーザーにはすでに「id」というフィールドがあります。
const users = [
{ id: 1, name: "太郎", email: "taro@example.com" },
{ id: 2, name: "花子", email: "hanako@example.com" },
{ id: 3, name: "健", email: "ken@example.com" },
];
JSXこの id は、
- データベースやAPIが「ユーザーを一意に識別するため」に持っているID
です。
React側でも、同じように「各要素を一意に識別するためのID」が必要になります。 それが key です。
つまり、
「データのidを、そのままReactのkeyとして使う」
というのが、一番自然で安全な形になります。
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
JSXここでの key={user.id} は、
- 「この
<li>は、idが1のユーザーに対応している」 - 「この
<li>は、idが2のユーザーに対応している」
という情報をReactに伝えています。
indexをkeyにしたときに起きる「ズレ」をイメージする
並びが変わると、indexも変わる
indexをkeyにした場合の問題を、もう少し具体的にイメージしてみます。
{users.map((user, index) => (
<li key={index}>{user.name}</li>
))}
JSXこのとき、Reactは「index」をIDとして扱います。
- index 0 → 太郎
- index 1 → 花子
- index 2 → 健
ここから「太郎」を削除すると、配列はこうなります。
- index 0 → 花子
- index 1 → 健
Reactから見ると、
- 「index 0の要素は前回と違うものになった」
- 「index 1の要素も前回と違うものになった」
というように見えてしまいます。
本当は「太郎だけが消えた」状態なのに、 「全部入れ替わった」ように誤解される可能性があるわけです。
入力フォーム付きのリストで起きる違和感
この「ズレ」は、入力フォーム付きのリストで特に分かりやすく現れます。
例えば、ユーザー一覧に「メモ入力欄」を付けたとします。
{users.map((user, index) => (
<li key={index}>
<p>{user.name}</p>
<input type="text" placeholder="メモ" />
</li>
))}
JSXユーザーごとにメモを入力している状態で、 先頭のユーザーを削除すると、
- 入力したメモが「別のユーザーの行」に移ってしまう
というような挙動が起きることがあります。
これは、
- Reactが「index 0の要素は同じもの」と誤認してしまう
- 実際には別のユーザーに対応する行になっている
というズレが原因です。
重要ポイント:
- indexは「配列の並び」に依存する値であり、 並び替え・削除・追加で簡単に変わってしまいます。
- そのため、「その要素を一意に識別するID」としては不適切です。
IDをkeyにすることで「ズレ」を防ぐ
データのIDは変わらない
一方、user.id をkeyにしている場合はどうでしょうか。
{users.map((user) => (
<li key={user.id}>
<p>{user.name}</p>
<input type="text" placeholder="メモ" />
</li>
))}
JSXここでは、
- id 1 → 太郎
- id 2 → 花子
- id 3 → 健
という対応関係があり、 削除や追加があっても「idそのもの」は変わりません。
先頭のユーザー(id 1)を削除すると、
- id 2 → 花子
- id 3 → 健
という状態になりますが、
Reactは、
- 「id 1の要素が消えた」
- 「id 2と3の要素は前回と同じものだ」
と認識できます。
その結果、
- 入力フォームやチェックボックスの状態が、正しいユーザーの行に残る
- アニメーションやトランジションも、意図した要素に対して適用される
という「自然な挙動」が保たれます。
重要ポイント:
- データのIDは「そのデータを一意に識別するための値」であり、 並び替えや削除・追加があっても変わりません。
- だからこそ、Reactのkeyとして使うのに適しています。
実践:ユーザー一覧でkeyの選び方を意識する
正しい形:IDをkeyにしたユーザー一覧
ここで、IDをkeyにしたユーザー一覧をもう一度整理しておきます。
import { useState } from "react";
function UserList() {
const [users, setUsers] = useState([
{ id: 1, name: "太郎", email: "taro@example.com" },
{ id: 2, name: "花子", email: "hanako@example.com" },
{ id: 3, name: "健", email: "ken@example.com" },
]);
const handleDelete = (id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
};
return (
<div>
<h2>ユーザー一覧</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
<p>名前:{user.name}</p>
<p>メール:{user.email}</p>
<button onClick={() => handleDelete(user.id)}>削除</button>
</li>
))}
</ul>
</div>
);
}
export default UserList;
JSXこのコードでは、
usersは「ユーザー情報のオブジェクト」が入った配列map()で各ユーザーをJSXに変換key={user.id}で、各ユーザーを一意に識別- 削除ボタンで配列を更新し、Reactがkeyを使って差分を正しく計算
という流れになっています。
indexをkeyにした場合との違いを意識する
あえて、indexをkeyにした場合も並べておきます。
{users.map((user, index) => (
<li key={index}>
<p>名前:{user.name}</p>
<p>メール:{user.email}</p>
<button onClick={() => handleDelete(user.id)}>削除</button>
</li>
))}
JSXこの形は、
- 「見た目は動く」
- 「警告も消える」
かもしれませんが、
- 並び替えや削除・追加が行われたときに、 Reactが「どの要素がどのユーザーに対応しているか」を誤認する可能性があります。
ここでの大事な感覚:
- 「indexをkeyにすると、Reactの視点と自分の視点がズレる」
- 「IDをkeyにすると、Reactの視点と自分の視点が一致する」
という違いを意識できると、keyの選び方に迷いがなくなります。
練習用テンプレート:ユーザー一覧+メモ入力付き
最後に、DAY 24中盤の内容をまとめた練習用テンプレートとして、 「ユーザー一覧+メモ入力付き」のコンポーネントを示します。
import { useState } from "react";
function UserListWithNotes() {
const [users, setUsers] = useState([
{ id: 1, name: "太郎", email: "taro@example.com" },
{ id: 2, name: "花子", email: "hanako@example.com" },
{ id: 3, name: "健", email: "ken@example.com" },
]);
const handleDelete = (id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
};
return (
<div>
<h2>ユーザー一覧(メモ入力付き)</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
<p>名前:{user.name}</p>
<p>メール:{user.email}</p>
<input type="text" placeholder="このユーザーに関するメモ" />
<button onClick={() => handleDelete(user.id)}>削除</button>
</li>
))}
</ul>
</div>
);
}
export default UserListWithNotes;
JSXこのテンプレートは、
- IDをkeyにした場合の「自然な挙動」を体感するための題材になります。
- 実際に削除を試してみると、
- 入力したメモが「別のユーザーの行」に移動したりしない
- 削除した行だけが消え、残りの行はそのまま
ということが確認できます。
DAY 24 中盤のまとめ
DAY 24の中盤では、
- Reactが「前回」と「今回」の仮想DOMを比べて差分更新していることを前提に、keyの役割を捉え直したこと
- keyが「ReactにとってのID」であり、データのIDと一致させることで、視点のズレを防げること
- indexをkeyにした場合に、並び替え・削除・追加で「同じ要素なのに別物として扱われる」ズレが起きることを具体的にイメージしたこと
- IDをkeyにすることで、入力フォームやチェックボックスを含むリストでも、自然な挙動が保たれること
- ユーザー一覧+メモ入力付きのテンプレートを通して、 「IDをkeyにするべき理由」を実際のUIの動きとして想像できるようになったこと
が大きなポイントになります。
この段階まで来ると、
- 「keyはReactのためのID」
- 「indexではなく、一意なIDを使う」
という原則が、単なる知識ではなく、 「動くUIを設計するための感覚」として自分の中に根付き始めているはずです。
DAY 24:keyを理解する 後半 ― 「実務レベルで通用するkeyの感覚」を仕上げる
DAY 24の後半では、これまで学んできた keyの考え方 を、 「実務レベルでもそのまま通用する」ところまで仕上げていきます。
ここからのゴールは、
keyとは / なぜ必要か / IDを使用する / indexをkeyにする問題を、迷いなく説明できるようになること- 「ユーザー一覧」を題材に、追加・削除・編集が絡む場面でも、正しいkey設計ができるようになること
- 「このリストは安全に動く」と自信を持って言えるコードを書けるようになること
です。
keyとは何かを「一言で説明できる」ようにする
Reactにとっての「その要素のID」です
後半の最初に、改めて key を一言でまとめておきます。
keyとは、Reactがリストの各要素を識別するために使う「その要素のID」です。
ここで大事なのは、
- 「人間にとってのID(ユーザーIDなど)」
- 「ReactにとってのID(key)」
を 一致させる という感覚です。
ユーザー一覧なら、
- データ側のID →
user.id - React側のID →
key={user.id}
という形で、1対1に対応させます。
この「IDの一致」が、後半で扱う「追加・削除・編集が絡むリスト」を安定して動かすための土台になります。
なぜkeyが必要かを「差分更新」の視点で整理する
Reactは「前回」と「今回」を比べている
Reactは、毎回ゼロからDOMを作り直しているわけではなく、
「前回の仮想DOM」と「今回の仮想DOM」を比べて、差分だけを実際のDOMに反映する」
という仕組みで動いています。
リスト表示の場合、この差分計算をするために、
- 「この要素は前回もあったものか」
- 「この要素は新しく追加されたものか」
- 「この要素は削除されたものか」
を判断する必要があります。
そのときに使われるのが key です。
ユーザー一覧でイメージする
例えば、次のようなユーザー一覧があるとします。
const usersBefore = [
{ id: 1, name: "太郎" },
{ id: 2, name: "花子" },
{ id: 3, name: "健" },
];
JSX削除や追加が行われて、次のようになったとします。
const usersAfter = [
{ id: 1, name: "太郎" },
{ id: 3, name: "健" },
{ id: 4, name: "新しいユーザー" },
];
JSXReactはここで、
- 「id 1の要素は前回も今回もある」
- 「id 2の要素はなくなった」
- 「id 3の要素は前回も今回もある」
- 「id 4の要素が新しく追加された」
ということを判断したいわけです。
この「誰が残っていて、誰がいなくなったか」を判断するために、 keyが「ReactにとってのID」として使われます。
IDを使用する ― 「データのID」と「Reactのkey」を揃える
ユーザー一覧での基本パターン
ユーザー一覧を例に、IDをkeyに使う基本パターンを整理します。
import { useState } from "react";
function UserList() {
const [users, setUsers] = useState([
{ id: 1, name: "太郎", email: "taro@example.com" },
{ id: 2, name: "花子", email: "hanako@example.com" },
{ id: 3, name: "健", email: "ken@example.com" },
]);
const handleDelete = (id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
};
return (
<div>
<h2>ユーザー一覧</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
<p>名前:{user.name}</p>
<p>メール:{user.email}</p>
<button onClick={() => handleDelete(user.id)}>削除</button>
</li>
))}
</ul>
</div>
);
}
export default UserList;
JSXここでのポイントは、
usersは「ユーザー情報のオブジェクト」が入った配列map()で各ユーザーをJSXに変換key={user.id}で、各ユーザーを一意に識別- 削除ボタンで配列を更新し、Reactがkeyを使って差分を正しく計算
という流れになっていることです。
重要ポイント:
- データ側のID(
user.id)とReact側のID(key)を一致させることで、 「この要素はこのユーザーに対応している」という関係が崩れません。 - これが、追加・削除・編集が絡むリストを安定して動かすための基本です。
indexをkeyにする問題を「実務的な違和感」として捉える
見た目は動くが、内部的には不安定
indexをkeyにした場合の問題は、 「見た目は動いているように見えるが、内部的には不安定」という点にあります。
{users.map((user, index) => (
<li key={index}>
<p>名前:{user.name}</p>
<p>メール:{user.email}</p>
<button onClick={() => handleDelete(user.id)}>削除</button>
</li>
))}
JSXこのコードは一見動きますし、Reactの警告も消えます。 しかし、並び替え・削除・追加が行われたときに、
- 「同じユーザーなのに、別の要素として扱われる」
- 「別のユーザーなのに、同じ要素として扱われる」
というズレが起きる可能性があります。
入力フォーム付きのユーザー一覧での違和感
特に分かりやすいのが、入力フォーム付きのユーザー一覧です。
{users.map((user, index) => (
<li key={index}>
<p>名前:{user.name}</p>
<p>メール:{user.email}</p>
<input type="text" placeholder="このユーザーに関するメモ" />
<button onClick={() => handleDelete(user.id)}>削除</button>
</li>
))}
JSXこの状態で、
- 各ユーザーにメモを入力する
- 先頭のユーザーを削除する
という操作をすると、
- 入力したメモが「別のユーザーの行」に移ってしまう
というような挙動が起きることがあります。
これは、
- Reactが「index 0の要素は同じもの」と誤認してしまう
- 実際には別のユーザーに対応する行になっている
というズレが原因です。
重要ポイント:
- indexは「配列の並び」に依存する値であり、 並び替え・削除・追加で簡単に変わってしまいます。
- そのため、「その要素を一意に識別するID」としては不適切です。
実践:ユーザー一覧を作成しながらkeyの設計を仕上げる
追加・削除・編集ができるユーザー一覧
ここで、DAY 24後半の仕上げとして、 「追加・削除・編集」ができるユーザー一覧を作ってみます。
import { useState } from "react";
function UserItem({ user, onDelete, onChangeName }) {
return (
<li>
<input
type="text"
value={user.name}
onChange={(e) => onChangeName(user.id, e.target.value)}
/>
<span style={{ marginLeft: "8px", marginRight: "8px" }}>
({user.email})
</span>
<button onClick={() => onDelete(user.id)}>削除</button>
</li>
);
}
function UserList() {
const [users, setUsers] = useState([
{ id: 1, name: "太郎", email: "taro@example.com" },
{ id: 2, name: "花子", email: "hanako@example.com" },
]);
const [nextId, setNextId] = useState(3);
const handleAddUser = () => {
const newUser = {
id: nextId,
name: `ユーザー${nextId}`,
email: `user${nextId}@example.com`,
};
setUsers((prevUsers) => [...prevUsers, newUser]);
setNextId((prevId) => prevId + 1);
};
const handleDeleteUser = (id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
};
const handleChangeUserName = (id, newName) => {
setUsers((prevUsers) =>
prevUsers.map((user) =>
user.id === id ? { ...user, name: newName } : user
)
);
};
return (
<div>
<h2>ユーザー一覧(追加・削除・編集付き)</h2>
<button onClick={handleAddUser}>ユーザーを追加</button>
<ul style={{ marginTop: "8px" }}>
{users.map((user) => (
<UserItem
key={user.id}
user={user}
onDelete={handleDeleteUser}
onChangeName={handleChangeUserName}
/>
))}
</ul>
</div>
);
}
export default UserList;
JSXこのコードで確認できること
- keyとは
key={user.id}が「ReactにとってのID」として機能しています。
- なぜ必要か
- 追加・削除・編集が行われても、 「idが同じユーザーに対応する行」は同じ要素として扱われます。
- IDを使用する
- データ側のID(
user.id)とReact側のID(key)が一致しているため、 入力内容や編集状態が正しいユーザーに紐づいたまま保たれます。
- データ側のID(
- indexをkeyにする問題
- もしここで
key={index}にしてしまうと、 削除や追加のたびに「どの行がどのユーザーに対応しているか」がズレる可能性があります。
- もしここで
重要ポイント:
- 実際に「追加・削除・編集」が絡むリストを考えると、 indexをkeyにする危険性がよりはっきり見えてきます。
- 一意なIDをkeyにすることで、 「この行はこのユーザーに対応している」という関係が、操作をまたいでも崩れません。
DAY 24 後半のまとめ
DAY 24の後半では、
keyとは何かを「ReactにとってのID」として一言で説明できるようにしたことなぜ必要かを、差分更新の仕組みとユーザー一覧の例から具体的にイメージしたことIDを使用することで、データ側のIDとReact側のIDを一致させ、「誰が誰なのか」がブレない状態を作れることindexをkeyにする問題を、入力フォーム付きのリストや追加・削除・編集が絡む場面での違和感として捉え直したこと- 実践として「ユーザー一覧(追加・削除・編集付き)」を実装し、 「一意なIDをkeyにすることが、動くUIを安定させるための必須条件である」という感覚をコードレベルで確認したこと
が大きなポイントになります。
ここまで来ると、
- 「keyは警告を消すためのもの」ではなく、
- 「Reactがリストを正しく理解し、動くUIを安定させるための中核的な仕組み」
として、しっかり自分の中に根付いているはずです。
