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の挙動は
- stateの更新を検知したらコンポーネントが再度上から評価される(再レンダリング)
- 関数内で同じ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)
の挙動の違いです。誤り等ありましたらご指摘いただけますと幸いです。