Reactのkey propに配列のindexを使うことが良くない理由
始め
Reactのmap
を使う時、key
エラーをなくすためindex
を使ったことがあります。しかし最近それがanti-patternだということを知りましたので、その理由をまとめました。
1. keyの存在意義
1-1. keyってなんだっけ
そういえばそもそもkey
って何で必要だったけ…?と、ふいと思ってしまいました。何となくは知ってますが、明確にしたいのでこの部分から始めましょう。
まずはこのサンプルコードをご覧ください。
export default function App() {
let fruits = [{ name: "apple" }, { name: "banana" }, { name: "pear" }];
return (
<div className="App">
{fruits.map((fruit) => (
<p>{fruit.name}</p>
))}
</div>
);
}
fruits
という配列をmap
メソッドを使ってpタグに入れてる簡単なコードです。これだけ見たら問題なさそうに見えますし、画面にもちゃんとでます。
しかし、console窓を確認したらこういうエラーが起きてました。
Warning: Each child in a list should have a unique "key" prop.
「リスト内の各childには、固有のkey prop
が必要です」という意味ですね。まだkey
が何なのか曖昧ですが、とりあえず固有のkey
が必要だと言ってるので入れます。
export default function App() {
let fruits = [
{ id: 1, name: "apple" },
{ id: 2, name: "banana" },
{ id: 3, name: "pear" }
];
return (
<div className="App">
{fruits.map((fruit) => (
<p key={fruit.id}>{fruit.name}</p>
))}
</div>
);
}
key prop
にid
を入れたら先のエラーが消えました。
key
が何をするやつなのかはReactの公式ドキュメントでも説明しています。
Key は、どの要素が変更、追加もしくは削除されたのかを React が識別するのに役立ちます。配列内の項目に安定した識別性を与えるため、それぞれの項目に key を与えるべきです。
この説明だけではピンとこないかもしれませんので、上のサンプルコードで例をあげてみます。
1-2. keyがない場合
fruits
の{ name: "apple" }
と{ name: "banana" }
の間に{ name: "melon" }
を追加します。動的に追加するのが普通ですが、わかりやすくするためにサンプルコードには追加された結果を入れました。
export default function App() {
let fruits = [
{ name: "apple" },
{ name: "melon" }, // 新しく追加
{ name: "banana" },
{ name: "pear" }
];
return (
<div className="App">
{fruits.map((fruit) => (
<p>{fruit.name}</p>
))}
</div>
);
}
皆さんご存知の通り、Reactは変更がある時に変更前と後を比較して変更がある部分だけ更新させます。ここで大事なのは、基本的に比較するときは上から順番に比較するということです。
この変化を反映させたら何が起こるかを絵で見てみましょう。
②の左が変更前、右が変更後です。上から順番に比較された結果、banana
がmelon
に変更、pear
がbanana
に変更、新規pear
追加の3つの更新が行われます。配列の中間に新しい要素一つ入れただけなのに、実際には変化のない要素まで更新されるなんて非効率的に感じますね。
1-3. keyがある場合
export default function App() {
let fruits = [
{ id: 1, name: "apple" },
{ id: 4, name: "melon" }, // 新しく追加
{ id: 2, name: "banana" },
{ id: 3, name: "pear" }
];
return (
<div className="App">
{fruits.map((fruit) => (
<p key={fruit.id}>{fruit.name}</p>
))}
</div>
);
}
ここでkey
が活躍してくれます。key
をつけると、Reactが比較するときにkey
を元に比較してくれます。つまり、上から順番通りではなくてkey
が同じ要素同士に比較するということです。結果、以下のように改善されます。
key
にid
入れただけなのに前回とは違って新規で追加された要素一つだけ更新されました。これで簡単に不要な更新を防げられます。
そして、ここまで理解したら薄々key
にindexを入れたら何かまずそうな気がしてきます。
2. indexが危険な理由
Robin Pokornyの「Index as a key is an anti-pattern」に良いデモがありましたので、試してみました。皆さんもやってみてください。
key
がindex
の場合とid
の場合の違い、わかりましたか?
私はきっとFooなんちゃらのinput
に数字を入力したのに、その上に新しいinput
を追加したら入力した内容も新しいinput
に行ってしまいました。
理由は簡単です。私が書いた「12345」は1番最初のinput
なのでindex
が0
、つまりkey=0
になってるはずです。しかし、先頭に新規input
を追加したらそれが0
番目のindex
、key=0
になってしまいます。Reactは「12345」はkey=0
のinput
のものだと判断します。結果、先頭に新規欄を追加したら入力内容がそこに行ってしまうということでしょう。
React公式ドキュメントでもこのような場合について言及しています。
要素の並び順が変更される可能性がある場合、インデックスを key として使用することはお勧めしません。パフォーマンスに悪い影響を与え、コンポーネントの状態に問題を起こす可能性があります。
3. まとめ
今までkey
にindex
を使うことは良くないと話しました。しかし、何があっても絶対使ってはいけないわけでもありません。React公式ドキュメントではkey
にindex
を使うことは最終手段だと表現してます。でしたら、index
もオッケーな場合はいつでしょうか?
「Index as a key is an anti-pattern」では、「以下の3つの条件をすべて満たしたらkey
にindex
を使ってもも安全でしょう」と説明しました。
- 配列とその中の要素が静的(計算も変更もされない)
- 配列の中の要素がidを持ってない
- 配列が
reorder
やfilter
されることが絶対ない
ですが、やはりできるだけkey
にはid
などの変わらない固有の値をを使ったほうがいいと思います。index
にしたらいつどこでバグるかもしれないという不安が残りますから。
終わり
割と簡単な内容でしたが、ふわふわしてた部分をはっきりできてよかったです😌
Discussion