<div>
{value ? <Foo/> : <Bar/>}
</div>
みたいなのがあるときで どちらの場合も同じコンポーネントになる場合は注意が必要です
上の場合の Bar も Foo になっていて value 問わずコンポーネントは Foo になり props の違いだけの場合
こういう記法の時点で書く側は別物のつもりだと思いますが React が認識する ReactElement のツリー構造ではどっちも同じ Foo です
なので props が変わっただけという扱いになりコンポーネントはアンマウントされず使いまわされます
基本は props が変わればそれに応じて表示も変わりますが中で state を持っていた場合には state が引き継がれることで予期しない動きになります
その対処のために key を指定する必要があります
key が変われば同じコンポーネントでも別物扱いになり 既存のコンポーネントがアンマウントされ新規のコンポーネントがマウントされるので state はクリアされます
key は配列を返す場合に使うことが主ですが それ以外で必要になるパターンです
これをみると
<div>
{value && <Foo/>}
<Foo/>
</div>
のように条件付きで表示する場合 これも key がないとズレてしまいそうに思います
value が false から true になって Foo が 2 つ並ぶようになるとき それまでにあった常に表示されている Foo は 2 つめになり新しい Foo が先に来るはずです
でも React 的には Foo が 1 つから 2 つになっただけで順番的に最初の Foo が使い回されるのじゃないかと思いました
しかし試してみるとそういった事は起きません
期待通りに新しい Foo が先に挿入されます
どうなってるの?と思いましたがよく考えてみると 表示しない場合でも {} の中に undefined や null が残ります
これを無視しなければどこに新しく追加されたか判断できそうです
なので 配列以外で key を考えないといけないのは ?: くらいで && の場合は気にしなくても大丈夫そうです
{} に undefined を残さずひとつにまとめてこういうことをする場合は期待する通りには動きませんが これは配列を使ってるわけなので key が必要なケースです
const Component = () => {
const elems = []
if (value) elems.push(<Foo/>)
elems.push(<Foo/>)
return <div>{elems}</div>
}
?: に key を使うとき 同じ階層で同じコンポーネントの場合 key が重複しないよう注意が必要です
const App = () => {
const [state, setState] = React.useState(false)
return (
<div>
<button onClick={() => setState(!state)}>switch</button>
{state ? <Foo key={1} value={1}/> : <Foo key={2} value={2} />}
{state ? <Foo key={2} value={3}/> : <Foo key={1} value={4} />}
</div>
)
}
1 つめの {} と 2 つめの {} では完全に別物のつもりですが同じコンポーネントで階層が同じなので 再レンダリングの前後で同じ key が別の場所にあると使い回されます
state を切り替えると key の 1 と 2 が入れ替わるので 配列の並び替えと同じ扱いになります
配列でラップしたり <></> でラップすると展開後に階層が同じであっても 別階層として扱えるので key の重複がありえそうならラップしてしまうと安全です
<div>
<button onClick={() => setState(!state)}>switch</button>
<>{<Foo key={state ? 1 : 2}/>}</>
<>{<Foo key={state ? 2 : 1}/>}</>
</div>
<div>
<button onClick={() => setState(!state)}>switch</button>
{[
[<Foo key={state ? 1 : 2 }/>],
<Foo key={state ? 2 : 1 }/>,
]}
</div>
同時に同じ階層に同じ key が出現した場合は 作り直しや使い回しが意図したとおりにならないだけでなく表示もおかしくなります
例えばこの場合 state が変わるタイミングで同じ要素が繰り返し作られて要素が増えていきます
<div>
<button onClick={() => setState(!state)}>switch</button>
{[1,2,3].map(x => <div key={state ? 1 : 2}>{x}</div>)}
</div>
こういう同時に同じ key が存在するケースはコンソールに key の重複エラーが出てるので気づけるはずです
同時ではない場合は正規の入れ替え目的として扱われてエラーにならないので見逃しやすいです