🙆

状態更新についてのTips(キューイング)

2023/03/09に公開

状態更新のタイミング

React は、状態の更新を処理する前に、イベントハンドラー内のすべてのコードが実行されるまで待機します。
イベントハンドラーとその中のコードが実行完了するまで UI が更新されないことも意味します。
バッチ処理とも呼ばれるこの動作により、 React アプリの実行が大幅に高速化されます。

状態変数を複数回更新する

下記コードを実行してみてください。
ボタンをクリックしても、1 ずつしか更新されません。
これは、状態変数が再レンダリング後まで変化しないからです。

import { useState } from "react";

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button
        onClick={() => {
          setNumber(number + 1);
          setNumber(number + 1);
          setNumber(number + 1);
        }}
      >
        +3
      </button>
    </>
  );
}

では下記の場合ではどうなのでしょうか?
ボタンを押すと 3 ずつ増えていきます。上のコードと何が違うのでしょうか?

setNumber 関数が引数として受け取る値は React が保有している状態です。(メモリ空間の状態変数です。)
つまり、React が保有している状態を書き換えていっているため、下記の表のようになる

キュー更新 n 戻り値
n => n + 1 0 0 + 1 = 1
n => n + 1 1 1 + 1 = 2
n => n + 1 2 2 + 1 = 3
import { useState } from "react";

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button
        onClick={() => {
          setNumber((n) => n + 1);
          setNumber((n) => n + 1);
          setNumber((n) => n + 1);
        }}
      >
        +3
      </button>
    </>
  );
}

まとめ

イベントハンドラーないで、状態を更新する関数(setState 関数)を複数回実行すると、最後に実行した数がメモリ上に保存される
再レンダリング後に、状態として返却される
setState 関数はレンダリング中に実行されるため、純粋関数として、結果のみを返す必要があります。
それらの内部から状態を設定したり、他の副作用を実行したりしないでください

余談(命名規則)

対応する状態変数の最初の文字で updater 関数の引数に名前を付けるのが一般的です。

setEnabled((e) => !e);
setLastName((ln) => ln.reverse());
setFriendCount((fc) => fc * 2);

より詳細なコードが必要な場合は、setEnabled(enabled => !enabled) のように完全な状態変数名を繰り返すか、setEnabled(prevEnabled => !prevEnabled) のようにプレフィックスを使用するという別の一般的な規則があります。

GitHubで編集を提案

Discussion