はじめに
Reactで key といえば、多くの場合はリストのレンダリングで利用されることが多いです。
items.map(item => (
<Item key={item.id} />
))
そのため、
- mapの時に書くもの
- 書かないと警告が出るもの
といった認識で止まっている方も多いのではないでしょうか。
(自分も最初はそれくらいの理解でした💦)
しかし、key の本質的な役割は、Componentが「同じものか、別のものか」をReactに伝えるためのものです。
この記事では、key の理解を深めるため、リストに指定する以外のユースケースを紹介したいと思います。
keyの役割とは?
Componentの「同一性」を判断するためのものです。
Reactは再レンダリング時に、前回のUIツリーと今回のUIツリーを比較して差分を更新します。
このときReactが差分の有無の判断材料として利用するのが key です。
具体的には以下のように扱われます。
keyが同じ → 同じComponentとして扱う(更新)
keyが違う → 別のComponentとして扱う(作り直し)
つまり、key は Componentを更新するか、破棄して新しく作るか を決めるための情報です。
※ keyは「再レンダリングを制御するもの」ではなく、あくまで Component の同一性を判断するための情報
keyが変わると何が起きるか
次のようなComponentがあるとします。
function Sample() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log("mount");
return () => console.log("unmount");
}, []);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
これを通常どおり使う場合:
<Sample />
- 親が再レンダリングされても
- propsが変わっても
Sample は同じComponentとして扱われ、
stateは保持され続けます。
一方で、次のように key を指定すると:
<Sample key={someValue} />
someValue が変わった瞬間、
- 以前の
Sampleは unmount される - 新しい
Sampleが mount される - stateは初期化される
という挙動になります。
リストのレンダリングで利用する理由
リストでは、
{items.map(item => (
<Item key={item.id} />
))}
という形で key を指定します。
これは、配列の並びが変わったときに
- どのComponentが
- どのComponentに対応しているか
をReactが正しく判断するためです。
つまり、mapで使う key も Componentの同一性を伝えるためのものです。
じゃあ、リスト以外には何に使えるの?
では、リストのレンダリング以外でどんな時に key を使えるのか?
理解の具体例として、以前実際にあった例を簡略化して紹介します。
値がState管理されてしまっているComponent
あるライブラリが、パラメータで渡した値をStateで管理しており、渡す値を変更しても表示が更新されない、という事がありました。
ライブラリ側のComponent(修正できない想定)
import React from "react";
export function MysteriousCreature(input: { initialWord: string }) {
// !!! パラメータの値をStateに入れてしまっている
const [memory, _] = React.useState(input.initialWord);
React.useEffect(() => {
console.log("👾 Creature mounted");
return () => console.log("💀 Creature unmounted");
}, []);
return (
<div style={{ border: "1px solid #ccc", padding: "12px", borderRadius: "8px" }}>
<p>👾 この生き物は記憶を持っているよ!</p>
<p>記憶:{memory || "(まだ何も覚えてない)"}</p>
</div>
);
}
このComponentは、
-
initialWordを初期値として受け取る - ただし、初回マウント時にしか使わない
- propsが変わってもstateは更新されない
という特徴を持っています。
利用側のコンポーネント
import React from "react";
import { MysteriousCreature } from "./mysterious-creature";
function App() {
const [word, setWord] = React.useState("");
const [memory, setMemory] = React.useState("");
return (
<div style={{ padding: "24px" }}>
<h2>keyでComponentを“作り直す”例</h2>
<div
style={{
border: "1px solid #ccc",
padding: "12px",
borderRadius: "8px",
}}
>
<MysteriousCreature initialWord={memory} />
<input
value={word}
onChange={(e) => setWord(e.target.value)}
placeholder="言葉を入れてね!"
/>
<button
onClick={() => setMemory(word)}
style={{
margin: "12px",
borderRadius: "8px",
}}
>
覚えさせる!
</button>
</div>
</div>
);
}
export default App;
これを実行してみると、以下のGifのように「記憶」の部分が書き換わってくれません。
対策:Componentにkeyを利用し、「別のComponent」にする
MysteriousCreature は外部ライブラリの実装であるため、呼び出し側で何とか対策をする必要があります。
ここで、keyを利用します。
※ 別Componentであれば、作り直されるReactの特性を利用します
<MysteriousCreature key={memory} initialWord={memory} />
memory が変わるたびに key も変わり、
ReactはComponentを 別物として扱うようになります。
その結果:
- 以前のComponentは unmount
- 新しいComponentが mount
- 初期化処理が再実行される
という流れになり、以下の様に期待する動作をする様になります。
終わりに
今回の例は「そもそもライブラリの実装に問題がある」というものではありますが、
key の性質を理解しておくと、別の事象でハマった際にも役に立つ場面があります。
少しでも key について理解を深めていただけたら幸いです。

