仕組みを理解するReactのkeyによるStateのリセット

目次

こんにちは。ハイヤールーの Iwata です。

本記事では React の key による State のリセットについて解説していきます。React の公式 Document のpreserving and resetting stateの内容について紹介します。

想定読者

  • フロントエンド初心者〜中級者
  • フロントエンドをメインでは触らない方

React key について

React の要素を一意に識別するために key 属性が使用されます。隣接要素間で一意な値を key に渡すことによって DOM ツリーを差分を検知します。そして、React は再レンダリングが必要な要素のみを効率的に更新できます。例として配列内の項目に一意な識別性を与えるといったことが挙げられます。

フォームなどで選択肢を複数選ぶことができる Selector を例にします。options には value と displayName を持つオブジェクトの配列が渡ってくるとします。また、選択済みのものを表示させる Component も用意しておきます。仕様としては表示されている閉じるボタンを押すと選択肢が解除されるものとして話を進めていきます。

コード例

ここでなぜリストのレンダリングに key が必要なのか考えてみましょう。

React は Virtual DOM をメモリ上に構築します。これは実際の DOM(Real DOM)とは異なります。

Virtual DOM は React コンポーネント内の DOM を、実際に描画されている DOM に変換させる役割を担います。React コンポーネントに状態の変化があれば、React が構築した Virtual DOM が差分を検知します。

この差分検知の際に、同じコンポーネントが同じ階層にいた場合、key がないと開発者が期待した状態をレンダリングしてくれないため、key を与える必要があります。key がないことによって Virtual DOM のどの部分と比較すればよいかの指定がないため、に場合によってはリストの全部を更新してしまいます。

(#1)

つまり key は同じ階層の React コンポーネントを一意に識別するためのものです。また、key がない場合はコンポーネントが保持している State は Tree 構造の同じ位置で保存されます。

Key を使った State のリセット

配列のレンダリングでないケースでも key を使って応用することができます。

例えば、あるコンポーネント(ComponentA)で state を保持しており、その state をある変数が変化したタイミングで初期化したいといったケースの場合、どのようなコードがかけるでしょうか?

React.useEffect を使ってその値が変化したときに更新する処理を次のように書くことはできます。

const ComponentA: React.FC = (props) => {
  // コンポーネント(ComponentA)の中の処理
  const [selectedValue, setSelectedValue] = React.useState<string[]>(
    defaultValue
  );

  React.useEffect(() => {
    setSelectedValue(defaultValue)
  }, [defaultValue])
  
};

しかし、先程の key の仕組みを理解していると React.useEffect を使わなくても解決できます。変化を監視したい変数を key に渡すことによって React.useEffect を使わなくても初期化することができるのです。

<ComponentA key={value} {...componentAProps} />

これは DOM ツリーを更新する際に同じ component でも異なる key に変わったタイミングで同じ props を渡していても差分検知の対象になり、再レンダリングが走るため、中の state が初期化されるのです。

同じ結果になるなら React.useEffect でも良いのでは?と思う方がいるかも知れませんが、useEffect を使った場合は unmount 時にも処理が呼ばれるため、余計にレンダリングが走ってしまいます。ただ、key による初期化はその Component の DOM が再構築されるということなので、そのコンポーネントが持っている state をすべて初期化してしまうのが注意点です。

Codesandbox で用意した MultiSelectorを例に見てみます。この例では MultiSelector と選択した値を表示する GridChip という Component が分かれています。MultiSelector 内での State はプルダウンで出てきた選択肢が選択済みのものと表示させるために保持しています。それとは別に Container 側で別の State が管理されており、この State によって今選択している選択肢を表示させる役割を担っています。

GridChip の閉じるボタンによって MultiSelector 内の State も変化させる必要があります。そこで React の Key を使っています。この Key がないと閉じるボタンを押して Selector のプルダウンを開いても選択済みのままになってしまうことになります。Codesandboxの key なしの Component を用意したので実際に動作を確認できます。

このように Key は同じ Tree 構造でも異なる State を管理したい場合に便利になります。

まとめ

React の key の仕組みについて解説しました。これらのことは公式のreact.dev で書かれている内容です。key を使った初期化は意外と知られていないこともあると思うので記事として紹介してみました。

参考文献

#1 https://react.dev/learn/preserving-and-resetting-state