setCount(count + 1) を1つのハンドラ内で2回呼んだ。countはいくつ増える?
解説
1しか増えません。同じハンドラ内でsetCount(count + 1)を2回書いても、どちらのcountもそのレンダリング時点の同じ値を参照しています。たとえばcountが0のとき、1回目も2回目もsetCount(0 + 1)を実行しているだけなので、結果は1です。これはReactのstateがスナップショットであるという性質によるものです。ハンドラが実行されている間、countの値はそのレンダリング時に確定した値で固定されており、setCountを呼んでもその場では変わりません。再レンダリングが起きて初めて新しい値が反映されます。選択肢Aの「2増える」を選んだ場合、setCountを呼ぶたびに即座にcountが更新されるというメンタルモデルを持っています。実際のReactはそうは動かないので、この誤解は早めに修正しておくと今後のバグを防げます。選択肢Cの「0のまま」やDの「エラー」にはなりません。setCount自体は正常に動作し、最後に呼ばれたsetCount(0 + 1)の結果として1がセットされます。関数型更新で正しく2増やす前の状態値に基づいて更新したい場合は、関数型更新(updater function)を使います。setCount(prev => prev + 1); setCount(prev => prev + 1);この書き方では、Reactが内部的にキューを管理し、1回目の更新結果をprevとして2回目に渡してくれます。countが0なら、1回目でprevは0→結果1、2回目でprevは1→結果2となり、期待どおり2増えます。どちらの書き方を使い分けるか使い分けの基準はシンプルです。新しい値を直接セットする場合 → setCount(5) や setName('Alice') のように値を直接渡す。入力フォームの値をそのままセットするようなケースがこれにあたる前の値に基づいて更新する場合 → setCount(prev => prev + 1) のように関数を渡す。カウンターの増減、配列への要素追加、トグルの切り替えなどはこちらを使う迷ったら関数型更新を使っておけば安全です。値を直接渡すケースでも関数型で書いて壊れることはなく、逆に関数型を使わなかったことでバグになるケースは実務でよく発生します。stateのバッチ更新を理解するReact 18以降では、イベントハンドラだけでなくsetTimeoutやPromiseの.then内でもstateの更新が自動的にバッチ処理されます。バッチ処理とは、複数のsetState呼び出しを1回の再レンダリングにまとめる仕組みです。これはパフォーマンス最適化として非常に重要ですが、「setStateを呼んだ直後に最新の値を読みたい」という場面では混乱の原因になります。setCount(count + 1)の直後にconsole.log(count)しても古い値が表示されるのはこのためです。更新後の値を使いたい場合は、useEffectでcountを依存配列に入れて監視するのが定石です。