React Stateの更新における prev に関する整理
1. 値によるステート更新の限界
useStateフックのセッター関数(例: setCount)に、
ステートの値を直接用いた式を渡す形式(値による更新)には、
意図しない結果を生じる可能性がある
例: カウンターの複数回更新
const [count, setCount] = useState(0);
const handleIncrement = () => {
// countが 0 のとき
setCount(count + 1); // 0 + 1
setCount(count + 1); // 0 + 1
};
// 実行結果: count は 1 、期待値は 2
原因: Stale Stateとバッチ処理
- 非同期性とバッチ処理: Reactはパフォーマンス最適化のため、同じイベント内で発生した複数のステート更新を一つにまとめて(バッチ処理)非同期に実行
-
Stale State: イベントハンドラ実行中の
countは、そのコンポーネントがレンダリングされた時点の値を保持している(これをステイル(古い)な値と呼ぶ)。上記の例では、
2回のsetCount呼び出し時、countの値は常に最初の0を参照している
2. 解決策: 関数によるステート更新 (prevの利用)
直前のステートの値に基づいて新しいステートを計算する場合、
セッター関数には値ではなく更新関数を渡す必要がある
この更新関数が受け取る引数が prev(直前のステート)
構文
setState(prev => {
// prev はその時点でキューにある最新の直前のステート値
return nextState;
});
例: カウンターの正確な複数回更新
const handleIncrement = () => {
setCount(prevCount => prevCount + 1); // 1. prevCount(0) + 1 = 1
setCount(prevCount => prevCount + 1); // 2. prevCount(1) + 1 = 2
};
// 実行結果: count は 2
prevの動作原理
- Reactは、関数形式の更新をキューに登録し、
バッチ処理時にキュー内の関数を順序通りに実行 - 各更新関数が実行される際、引数(
prev)には、
一つ前の更新が適用された後の最新のステート値が渡される
3. prev利用時の留意事項: 不変性(Immutability)
prevとして渡される値(オブジェクトや配列)は、読み取り専用として扱うべきであり、
直接内容を変更(ミューテート)してはならない
ステートがオブジェクトや配列の場合、prevの値を用いて
新しいオブジェクトや配列を作成し、それを返却する必要がある(不変性の確保)
例: オブジェクトの更新
// 間違い: prevをミューテートしている
setPerson(prevPerson => {
prevPerson.age += 1; // 直接変更 (ミューテーション)
return prevPerson;
});
// 正しい: スプレッド構文で新しいオブジェクトを作成
setPerson(prevPerson => ({
...prevPerson, // 既存プロパティのコピー
age: prevPerson.age + 1 // 新しい値
}));
4. まとめ
| 更新方法 | 形式 | 適用条件 | 理由 |
|---|---|---|---|
| 関数による更新 | setState(prev => ...) |
直前のステートに依存して計算する場合。 | ステイルステートの問題を回避し、更新の順序性を保証するため。 |
| 値による更新 | setState(newValue) |
直前のステートに依存せず、値を上書きする場合。 | ステートに特定の値(例: 'default'や0)を直接設定する場合など。 |