一連の状態更新をキューに入れる
状態変数を設定すると、別のレンダーがキューに入ります。しかし、次のレンダーをキューに入れる前に、値に対して複数の操作を実行したい場合もあるでしょう。そのためには、Reactが状態更新をどのようにバッチ処理するかを理解することが役立ちます。
学習内容
- 「バッチ処理」とは何か、そしてReactがそれをどのように使用して複数の状態更新を処理するか
- 同じ状態変数に対して連続して複数の更新を適用する方法
Reactは状態更新をバッチ処理する
「+3」ボタンをクリックすると、カウンターが3回インクリメントされると予想するかもしれません。なぜなら、setNumber(number + 1)を3回呼び出しているからです:
しかし、前のセクションで思い出したかもしれませんが、各レンダーの状態値は固定されています。したがって、最初のレンダーのイベントハンドラ内のnumberの値は、何回setNumber(1)を呼び出しても、常に0です:
しかし、ここにはもう一つの要因が働いています。Reactは、イベントハンドラ内のすべてのコードが実行されるまで、状態更新の処理を待ちます。これが、再レンダーがこれらのすべての後にのみ発生する理由です。setNumber()呼び出しの
これは、レストランで注文を受けるウェイターを思い出させるかもしれません。ウェイターは、最初の料理の名前を聞いた瞬間に厨房に走りません!代わりに、あなたが注文を終えるのを待ち、変更を加えることを許し、さらにテーブルの他の人からの注文も受けます。

イラスト:Rachel Lee Nabors
これにより、複数の状態変数(さらには複数のコンポーネントからのものも)を更新でき、過度な再レンダーを引き起こすことなく済みます。しかし、これはまた、UIがあなたのイベントハンドラとその中のコードが完了するまで更新されないことも意味します。この動作は、バッチ処理とも呼ばれ、Reactアプリを大幅に高速化します。また、一部の変数のみが更新された「不完全な」レンダーに対処する混乱も避けられます。
Reactは、クリックのような複数の意図的なイベントにまたがってバッチ処理を行いません—各クリックは個別に処理されます。Reactは一般的に安全な場合にのみバッチ処理を行うことを保証します。これにより、例えば最初のボタンクリックでフォームが無効化された場合、2回目のクリックで再度送信されることはありません。
次のレンダーの前に同じ状態を複数回更新する
これは珍しいユースケースですが、次のレンダーの前に同じ状態変数を複数回更新したい場合、次の状態値を渡す代わりに、キュー内の前の状態に基づいて次の状態を計算するsetNumber(number + 1)のように関数を渡すことができます。例えば、setNumber(n => n + 1)のようにします。これは、単に状態を置き換えるのではなく、「状態値に対して何かを行う」ようにReactに指示する方法です。
今すぐカウンターをインクリメントしてみてください:
ここで、n => n + 1は更新関数と呼ばれます。これを状態セッターに渡すと:
- Reactは、イベントハンドラ内の他のすべてのコードが実行された後に処理されるよう、この関数をキューに入れます。
- 次のレンダー中に、Reactはキューを処理し、最終的な更新された状態を提供します。
以下は、イベントハンドラを実行しながら、Reactがこれらのコード行をどのように処理するかです:
setNumber(n => n + 1):n => n + 1は関数です。Reactはそれをキューに追加します。setNumber(n => n + 1):n => n + 1は関数です。Reactはそれをキューに追加します。setNumber(n => n + 1):n => n + 1は関数です。Reactはそれをキューに追加します。
次のレンダリング時にuseStateを呼び出すと、Reactはキューを処理します。前回のnumber状態は0だったので、Reactは最初の更新関数にn引数としてそれを渡します。次に、Reactは前の更新関数の戻り値を取り、次の更新関数にnとして渡し、以下同様に処理します:
| キューに追加された更新 | n | 戻り値 |
|---|---|---|
n => n + 1 | 0 | 0 + 1 = 1 |
n => n + 1 | 1 | 1 + 1 = 2 |
n => n + 1 | 2 | 2 + 1 = 3 |
Reactは最終結果として3を保存し、useStateからそれを返します。
これが、上記の例で「+3」をクリックすると値が正しく3増加する理由です。
状態を置き換えた後に更新するとどうなるか
このイベントハンドラーはどうでしょうか?次のレンダリングでnumberはどうなると思いますか?
このイベントハンドラーがReactに指示することは次の通りです:
setNumber(number + 5):numberは0なので、setNumber(0 + 5)です。Reactは「5で置き換える」をキューに追加します。setNumber(n => n + 1):n => n + 1は更新関数です。Reactはその関数をキューに追加します。
次のレンダリング中に、Reactは状態キューを処理します:
| キューに入った更新 | n | 戻り値 |
|---|---|---|
”5で置き換え” | 0(未使用) | 5 |
n => n + 1 | 5 | 5 + 1 = 6 |
Reactは最終結果として6を保存し、それをuseStateから返します。
注意
お気づきかもしれませんが、setState(5)は実際にはsetState(n => 5)のように動作しますが、nは使用されません!
更新後に状態を置き換えるとどうなるか
もう一つ例を試してみましょう。次のレンダリングでnumberはどうなると思いますか?
このイベントハンドラを実行する間、Reactがこれらのコード行を処理する方法は次のとおりです:
setNumber(number + 5):numberは0なので、setNumber(0 + 5)となります。Reactは“5で置き換え”をキューに追加します。setNumber(n => n + 1):n => n + 1は更新関数です。Reactはその関数をキューに追加します。setNumber(42):Reactは“42で置き換え”をキューに追加します。
次のレンダリング中、Reactは状態キューを処理します:
| キューに入った更新 | n | 戻り値 |
|---|---|---|
”5で置き換え” | 0(未使用) | 5 |
n => n + 1 | 5 | 5 + 1 = 6 |
”42で置き換え” | 6(未使用) | 42 |
その後、Reactは最終結果として42を保存し、useStateからそれを返します。
まとめると、setNumberステートセッターに渡す内容は次のように考えることができます:
- 更新関数(例:
n => n + 1)はキューに追加されます。 - その他の値(例:数値
5)は、キューに既にあるものを無視して「5で置き換え」をキューに追加します。
イベントハンドラーの完了後、Reactは再レンダリングをトリガーします。再レンダリング中、Reactはキューを処理します。更新関数はレンダリング中に実行されるため、更新関数は純粋で、結果のみを返す必要があります。その内部でステートを設定したり、他の副作用を実行したりしないでください。Strict Modeでは、Reactは間違いを見つけるために各更新関数を2回実行します(ただし2回目の結果は破棄します)。
命名規則
更新関数の引数は、対応するステート変数の最初の文字で命名するのが一般的です:
より詳細なコードを好む場合は、別の一般的な規則として、ステート変数名全体を繰り返す(例:setEnabled(enabled => !enabled))か、setEnabled(prevEnabled => !prevEnabled)のようにプレフィックスを使用します。
まとめ
- ステートを設定しても、現在のレンダリングの変数は変更されませんが、新しいレンダリングが要求されます。
- Reactは、イベントハンドラーの実行が完了した後にステート更新を処理します。これはバッチ処理と呼ばれます。
- 1つのイベントでステートを複数回更新するには、
setNumber(n => n + 1)のような更新関数を使用できます。
いくつかのチャレンジを試してみましょう
Challenge 1 of 2:リクエストカウンターを修正する #
あなたは、ユーザーが同じアートアイテムに対して複数の注文を同時に送信できるアートマーケットプレイスアプリに取り組んでいます。ユーザーが「購入」ボタンを押すたびに、「保留中」カウンターが1つ増える必要があります。3秒後、「保留中」カウンターが減少し、「完了」カウンターが増える必要があります。
しかし、「保留中」カウンターは意図した通りに動作しません。「購入」を押すと、-1に減少します(これは起こり得ないはずです!)。また、2回素早くクリックすると、両方のカウンターが予測不可能な動作をしているように見えます。
なぜこのようなことが起こるのでしょうか?両方のカウンターを修正してください。
