10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【React】setCount(count + 1) と setCount((prevCount) => prevCount + 1) の違い

Last updated at Posted at 2024-11-10

Reactの教材では、カウントボタンを例にuseStateの説明がされていることが多いと思います。

export const App = () => {
  const [count, setCount] = useState(0);
  const handleCountUp = () => {
    setCount(count + 1);
  }

  return (
    <>
      <button onClick={handleCountUp}>カウント</button>
      <p>{count}</p>
    </>
  )
}

カウントボタンを押して2ずつ増やしたい場合、setCount(count + 1)を2行書いてボタンを押しても実際は1しか増えず、setCount((prevCount) => prevCount + 1)のようにアロー関数の形にして2行書くことで2ずつ増えるようになります。これもセットで説明されていることが多いと思います。

この理由がいまいち腑に落ちず「とりあえずそういう風に書けばいいんだな」という程度で済ませていたのですが、自分なりに咀嚼できたのでまとめます。

ここで理解しておくべきReactの挙動は

  1. stateの更新を検知したらコンポーネントが再度上から評価される(再レンダリング)
  2. 関数内で同じset関数が複数回実行されていても、関数内の処理が終わるまでは更新が反映されない

ということです。

2については、公式ドキュメント(一連の state の更新をキューに入れる – React)に以下のように記載があります。

イベントハンドラ内のすべてのコードが実行されるまで、React は state の更新処理を待機します。このため、再レンダーはこれらの setNumber() 呼び出しがすべて終わった後で行われます。

イベントハンドラおよびその中のコードがすべて完了した後まで、UI は更新されないということでもあります。このような動作はバッチ処理(バッチング)とも呼ばれ、これにより React アプリの動作が格段に素早くなります。またこれは、変数のうち一部のみが更新された「中途半端な」レンダー結果に混乱させられずに済むということでもあります。

バッチ処理は「データを一定時間あるいは一定量ごとにまとめて処理する」という意味です。
1回の更新のたびにちまちまコンポーネントを上から評価してしまうとパフォーマンスが悪くなってしまうため、このような挙動になっている、ということですね。

たとえばカウントが0の状態で2つ増やす場合、

const handleCountUp = () => {
  setCount(count + 1);
  setCount(count + 1);
}

と書けばいいように思えるのですが、実際は以下のようになっています。

const handleCountUp = () => {
  setCount(0 + 1); // 0 + 1 = 0 だが、まだ関数内の処理が終わっていないため更新されず、stateは0のままで次の処理に移る
  setCount(0 + 1); // 現在のstateは0なので0が代入される => 関数内の処理が終了して更新が反映される、stateは1になる
}

実行中の関数内の処理が終わるまではsetCountが複数回実行されていても更新が反映されません。つまりこの2行は全く同じことをしています。
よって、ボタンを押しても1しか増えないのです。

前述したように、2つずつ増やす場合は以下のようにアロー関数の形で更新します。

const handleCountUp = () => {
  setCount((prevCount) => prevCount + 1);
  setCount((prevCount) => prevCount + 1);
}

アロー関数の形にすることで、現在のstateに+1してそれを新しいstateとして返すようReact側で実装してくれています。先程の公式ドキュメントによると 更新用関数 と呼ぶそうです。つまり、関数内のすべての処理の終了を待たずにstateの更新を反映させることができます。

const handleCountUp = () => {
  setCount((0) => 0 + 1); // stateが1に更新される
  setCount((1) => 1 + 1); // 現在のstateは1なので1が代入される => 2に更新される
}

あとは前述した通り、関数内のすべての処理が終わればコンポーネントが上から再評価されるため、ボタンを押すことでカウントが0から2になります。

以上、自分なりに咀嚼したsetCount(count + 1)setCount((prevCount) => prevCount + 1)の挙動の違いです。誤り等ありましたらご指摘いただけますと幸いです。

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?