【入門編】 関数型更新(Functional Updates)の活用 – React実践ガイド

こんにちは!Reactの学習、楽しく進めていますか?
画面がスイスイ動くインタラクティブなWebアプリが作れるようになると、本当にワクワクしますよね。

でも、Reactを触り始めてすぐの頃、誰もが一度は「あれ…? なんか状態(State)の値がおかしいぞ…?」と首をかしげる瞬間に出会います。ボタンを連打しているのに数字がうまく増えなかったり、非同期のタイミングで古いデータを掴んでしまったり。

「私の書き方が悪いのかな…」と落ち込む必要はまったくありません!これ、React初学者が必ずといっていいほど通る「最初の登竜門」なんですから。

今回は、そんな状態更新のモヤモヤを綺麗に解消してくれる魔法のテクニック、「関数型更新(Functional Updates)」について、お買い物のレジの行列にたとえながら、優しく紐解いていきましょう。

—

1. なぜ「状態の更新」でズレが生じるのか?

まずは、私たちが普段よく使う `useState` の世界をのぞいてみましょう。

例えば、ボタンを押すたびにカウントが「1つずつ」増えていくシンプルなカウンターを作るとします。

// カウントの数値を保持する状態(初期値は0)
const [count, setCount] = useState(0);

// ボタンが押されたときに呼ばれる関数
const handleIncrement = () => {
setCount(count + 1);
};

「今の `count` に `1` を足してセットする」――一見、完璧なコードに見えますよね。普通のプログラミング言語ならこれでバッチリです。

しかし、Reactには「状態の更新はすぐには反映されず、ちょっとだけ後回しにされる(非同期処理)」というちょっぴり気まぐれな性格があります。

例え話:お会計の「レジの行列」

これを身近な例で考えてみましょう。
あなたは今、お買い物のレジに並んでいます。いまあなたの手元には「カゴの中身:0個」というメモがあります。

ここで、店員さんから「あ、カゴに商品を1個追加してください!」と頼まれました。
あなたは、
1. 現在のメモ「0」を見る
2. 「0 + 1 = 1」と計算する
3. 新しいメモに「1」と書き直す

ここまでが通常の `setCount(count + 1)` の動きです。

では、「ものすごいスピードで2回連続で商品を追加して!」と頼まれたらどうなるでしょう?
レジの列の裏側(Reactの裏側)では、こんなことが起きています。

1. 店員Aさんがあなたに「1個追加して!」と頼む(まだメモは書き換わっていない)。
2. すぐ後ろから店員Bさんも「1個追加して!」と頼む(こちらも、まだ最初のメモの「0」を見ている)。
3. あなたは最初の「0」をもとに、「0 + 1 = 1」と計算してメモを「1」にする。
4. その直後、店員Bさんの頼みも「0」をもとに計算されてしまい、「0 + 1 = 1」としてメモを上書きしてしまう。

あれっ!? 2回頼まれたのに、トータルのカウントが「2」にならず「1」のままになってしまいました!

これが、Reactで「古い状態の `count`」をそのまま計算に使ってしまうことで起きる、有名なデータのズレ(バグ)の正体です。

—

2. 救世主!「関数型更新(Functional Updates)」とは?

この「古いメモを見ちゃう問題」を華麗に解決するのが、今回主役の「関数型更新」です。

書き方はとってもシンプル。`setCount` の中身に、直接数字を入れるのではなく、「最新の状態(引数)を受け取って、新しい状態を返す関数」を渡してあげます。

実際のコードを見てみましょう。

const handleIncrement = () => {
// setCountの中に「関数」を渡す!
// (prevCount) というのは、「その瞬間の最新のカウント(お肉や野菜が入ったホカホカのカゴの中身)」を指します。
setCount((prevCount) => prevCount + 1);
};

この `prevCount`(名前は何でもいいですが、previous=前の状態、という意味で `prev` や `oldState` と書くのが一般的です)を使うやり方を、関数型更新と呼びます。

レジの行列でたとえると…?

関数型更新を使った場合、先ほどのお買い物はこう変わります。

あなたはメモを直接見るのをやめました。代わりに、目の前にある「現在のリアルタイムのカゴの中身」を直接のぞき込みながら計算するルールに変えたのです。

1. 店員Aさんが「1個追加して!」と言う。あなたは現在のカゴ(0個)を見て、「0 + 1 = 1個」にする。
2. すぐ後ろの店員Bさんが「1個追加して!」と言う。あなたは今書き換わったばかりのカゴ(1個)をのぞき込み、「1 + 1 = 2個だ!」と計算する。

これなら、何度連続で頼まれても、絶対に計算がズレませんよね!

—

3. 実践!安全なカウンターを作ってみよう

それでは、実際に動かせるコンポーネントのコードを見てみましょう。
そのままコピペして、ご自身のReact環境やSandboxなどで試してみてください。

import React, { useState } from ‘react’;

export default function SafeCounter() {
// カウントの状態を定義(初期値は 0)
const [count, setCount] = useState(0);

// 【安全なパターン】関数型更新を使ったカウントアップ
const handleTripleIncrement = () => {
// 3回連続でボタンが押されたような状況を想定しても、
// 関数型更新を使っていれば確実に「+3」されます!
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
};

// リセットボタン
const handleReset = () => {
setCount(0);
};

return (

);
}

このコードの「一気に 3 増やす!」ボタンを押してみてください。
関数型更新のおかげで、カウントは迷うことなく「+3」ずつ確実に増えていきます。もしここを生の `setCount(count + 1)` にしていたら、3回書いても「+1」しか増えなくて頭を抱えることになっていたはずです。

—

4. いつ「関数型更新」を使うべき?(現場の判断基準)

「じゃあ、これからはすべての `useState` でこの書き方をしなきゃいけないの?」と思われるかもしれませんが、そんなことはありません。使い分けの基準はとてもシンプルです。

  • 「今の値」に依存しない更新のとき
  • 例:ユーザーが入力フォームに入れた新しい名前をそのまま保存する(`setName(‘山田太郎’)`)
  • 例:モーダルを開閉するフラグを強制的に `true` にする(`isOpen(true)`)
  • 👉 従来の書き方でOK!
  • 「前の状態(直前の値)」に依存して計算を変えるとき
  • 例:カウンターをプラスマイナスする(`count + 1`)
  • 例:Todoリストの配列に新しいアイテムをポイッと追加する(`[…prevTodos, newTodo]`)
  • 例:ショッピングカートの数量を増やす・減らす
  • 👉 絶対に「関数型更新」を使いましょう!

特に、配列やオブジェクトなどの「参照型データ」を扱うときは、古い状態を捕まえていると思わぬバグ(データの消失や画面が再描画されない問題)を引き起こしやすいため、関数型更新の習慣をつけておくと実務ですごく重宝されます。

—

まとめ

いかがでしたでしょうか?
Reactの `useState` における関数型更新は、最初はちょっと見慣れない書き方に見えるかもしれませんが、要するに「直前の最新の状態を確実にバトンタッチしてもらうための安全装置」です。

  • `setCount(count + 1)` だと、古い記憶のまま計算してしまってズレることがある。
  • `setCount(prevCount => prevCount + 1)` なら、その瞬間の最新データを確実に拾いに行ける!

この基本を押さえておくだけで、あなたの書くReactコードの信頼性はグッとプロのクオリティに近づきます。

エラーに出会ったり、「あれ、なんか挙動がおかしいな」と思ったときは、深呼吸をして「これ、古い状態を掴んじゃってないかな?」と思い出してみてくださいね。
あなたのReactライフが、もっと楽しく、もっとスムーズになりますように。応援しています!

コメント

タイトルとURLをコピーしました