v19.2Latest

スナップショットとしてのState

State変数は、読み書き可能な通常のJavaScript変数のように見えるかもしれません。しかし、Stateはスナップショットのように振る舞います。Stateを設定しても、既存のState変数は変更されず、代わりに再レンダリングがトリガーされます。

このページで学べること
  • Stateの設定がどのように再レンダリングをトリガーするか
  • Stateがいつ、どのように更新されるか
  • Stateを設定した直後にすぐに更新されない理由
  • イベントハンドラがStateの「スナップショット」にどのようにアクセスするか

Stateの設定がレンダリングをトリガーする

ユーザーインターフェースは、クリックのようなユーザーイベントに直接応答して変化すると考えるかもしれません。Reactでは、このメンタルモデルとは少し異なる仕組みで動作します。前のページで、Stateを設定するとReactに再レンダリングが要求されることを確認しました。これは、インターフェースがイベントに反応するためには、Stateを更新する必要があることを意味します。

この例では、「送信」ボタンを押すと、setIsSent(true)がReactにUIの再レンダリングを指示します:

ボタンをクリックしたときに起こることは以下の通りです:

  1. onSubmitイベントハンドラが実行されます。
  2. setIsSent(true)isSenttrueに設定し、新しいレンダリングをキューに入れます。
  3. Reactは新しいisSentの値に従ってコンポーネントを再レンダリングします。

Stateとレンダリングの関係について、詳しく見ていきましょう。

レンダリングはその時点でのスナップショットを取る

「レンダリング」とは、Reactがコンポーネント(関数)を呼び出すことを意味します。その関数から返されるJSXは、その時点でのUIのスナップショットのようなものです。そのprops、イベントハンドラ、ローカル変数はすべて、レンダリング時のStateを使用して計算されます。

写真や映画のフレームとは異なり、あなたが返すUIの「スナップショット」はインタラクティブです。入力に応じて何が起こるかを指定するイベントハンドラのようなロジックが含まれています。Reactはこのスナップショットに合わせて画面を更新し、イベントハンドラを接続します。その結果、ボタンを押すとJSX内のクリックハンドラがトリガーされます。

Reactがコンポーネントを再レンダリングするとき:

  1. Reactはあなたの関数を再度呼び出します。
  2. あなたの関数は新しいJSXスナップショットを返します。
  3. Reactはその後、あなたの関数が返したスナップショットに合わせて画面を更新します。
  1. 関数を実行するReact
  2. スナップショットの計算
  3. DOMツリーの更新

イラスト提供:Rachel Lee Nabors

コンポーネントの記憶としてのstateは、関数が返った後に消える通常の変数のようではありません。stateは実際にはReact自体の中に——まるで棚の上のように!——あなたの関数の外側で「存在」しています。Reactがあなたのコンポーネントを呼び出すとき、その特定のレンダリング用のstateのスナップショットをあなたに渡します。あなたのコンポーネントは、そのレンダリング時のstateの値を使って計算された、新しいpropsとイベントハンドラ一式を含むUIのスナップショットをJSXで返します。

  1. あなたがReactにstateの更新を指示する
  2. Reactがstateの値を更新する
  3. Reactがstateの値のスナップショットをコンポーネントに渡す

イラスト提供:Rachel Lee Nabors

これがどのように機能するかを示す小さな実験です。この例では、「+3」ボタンをクリックすると、を3回呼び出すため、カウンターが3回インクリメントされると予想するかもしれません。

「+3」ボタンをクリックしたときに何が起こるか見てみましょう:

クリックごとにnumberが1回しか増加しないことに注目してください!

状態を設定しても、それが変更されるのは次回のレンダリング時です。最初のレンダリングでは、number0でした。そのため、そのレンダリング時のonClickハンドラ内では、setNumber(number + 1)が呼び出された後でも、numberの値は依然として0のままです:

このボタンのクリックハンドラがReactに指示する内容は以下の通りです:

  1. setNumber(number + 1):number0なので、setNumber(0 + 1)となります。
    • Reactは次のレンダーでnumber1に変更する準備をします。
  2. setNumber(number + 1):number0なので、setNumber(0 + 1)となります。
    • Reactは次のレンダーでnumber1に変更する準備をします。
  3. setNumber(number + 1):number0なので、setNumber(0 + 1)となります。
    • Reactは次のレンダーでnumber1に変更する準備をします。

たとえsetNumber(number + 1)を3回呼び出したとしても、このレンダーのイベントハンドラ内ではnumberは常に0なので、状態を1に3回設定することになります。これが、イベントハンドラが終了した後、Reactがコンポーネントをnumber3ではなく1に等しい状態で再レンダリングする理由です。

また、コード内の状態変数をその値で置き換えて考えることで、これを視覚化することもできます。number状態変数は0なので、そのイベントハンドラは次のようになります:このレンダーでは

次のレンダリングでは、number1なので、そのレンダリングのクリックハンドラーは次のようになります:

これが、ボタンを再度クリックするとカウンターが2に設定され、次にクリックすると3になる理由です。

時間経過に伴う状態

さて、これは面白かったですね。このボタンをクリックすると何がアラートされるか、推測してみてください:

先ほどの置換法を使えば、アラートが「0」を表示すると推測できます:

しかし、アラートにタイマーを設定して、コンポーネントが再レンダリングされたにのみ発火するようにしたらどうでしょうか?「0」と表示されるでしょうか、それとも「5」と表示されるでしょうか?予想してみてください!

驚きましたか?置換法を使えば、アラートに渡される状態の「スナップショット」が見えます。

アラートが実行される時点では、Reactに保存されている状態は変わっているかもしれませんが、それはユーザーが操作した時点の状態のスナップショットを使ってスケジュールされたのです!

状態変数の値は、1回のレンダー内では決して変化しません。たとえそのイベントハンドラのコードが非同期であってもです。そのレンダーのonClick内では、numberの値は0のままです。setNumber(number + 5)が呼び出された後でもです。その値は、Reactがコンポーネントを呼び出してUIの「スナップショットを撮った」時に「固定」されたのです。

これにより、イベントハンドラがタイミングのミスを起こしにくくなる例を示します。以下は、5秒の遅延を伴ってメッセージを送信するフォームです。このシナリオを想像してください:

  1. 「送信」ボタンを押して、「Hello」をAliceに送信します。
  2. 5秒の遅延が終わる前に、「To」フィールドの値を「Bob」に変更します。

アラートには何が表示されると予想しますか

Reactは、1回のレンダーのイベントハンドラ内で状態の値を「固定」しておきます。コードが実行されている間に状態が変化したかどうかを心配する必要はありません。

しかし、再レンダーの前に最新の状態を読み取りたい場合はどうすればよいでしょうか?その場合は、状態更新関数を使用することになります。これは次のページで説明します!

まとめ

  • 状態を設定すると、新しいレンダーが要求されます。
  • Reactは状態をコンポーネントの外側、まるで棚の上にあるかのように保存します。
  • あなたがuseStateを呼び出すと、Reactはそのレンダーのための状態のスナップショットを渡します。
  • 変数とイベントハンドラは再レンダーを「生き延びる」ことはありません。すべてのレンダーは独自のイベントハンドラを持ちます。
  • すべてのレンダー(およびその中の関数)は、常にReactがそのレンダーに渡した状態のスナップショットを「見る」ことになります。
  • イベントハンドラ内の状態を、レンダリングされるJSXについて考えるのと同様に、頭の中で置き換えることができます。
  • 過去に作成されたイベントハンドラは、それらが作成されたレンダーの状態の値を持っています。

いくつかのチャレンジを試してみる

Challenge 1 of 1:信号機を実装する #

ボタンを押すと切り替わる横断歩道の信号コンポーネントがあります:

クリックハンドラーに alert を追加してください。信号が緑で「Walk」と表示されているとき、ボタンをクリックすると「Stop is next」と表示されるようにします。信号が赤で「Stop」と表示されているとき、ボタンをクリックすると「Walk is next」と表示されるようにします。

alertsetWalk 呼び出しの前と後ろのどちらに置くかで違いはありますか?