レッスン一覧に戻る

状態の更新パターン

レッスン 5

状態を更新する様々な方法と、それぞれの使い分けを学びます

推定学習時間: 30分

🎯 このレッスンで学ぶこと

状態の更新には様々なパターンがあります。適切な更新方法を選ぶことで、 コードが読みやすくなり、バグを防ぐことができます。

このレッスンでは、以下の内容を学びます:

  • 直接値での更新: シンプルな値の更新方法
  • 関数型更新: 前の値を使った安全な更新方法
  • オブジェクトの更新: スプレッド構文を使った更新
  • 配列の更新: 追加、削除、更新のパターン
  • 条件付き更新: 特定の条件での更新

1. 基本的な状態更新

最もシンプルな更新方法は、新しい値を直接渡すことです。 文字列や数値などの単純な値の場合、この方法が最も適しています。

📝 基本的な更新の例

import { useState } from "react"; const Counter = () => { const [count, setCount] = useState(0); const [name, setName] = useState(""); const [isActive, setIsActive] = useState(false); return ( <div> <button onClick={() => setCount(5)}> カウントを5に設定 </button> <input value={name} onChange={(e) => setName(e.target.value)} /> <button onClick={() => setIsActive(true)}> アクティブにする </button> </div> ); };

💡 新しい値を直接渡すだけで、状態を更新できます。

2. 関数型更新(前の値を使う)

前の値を使って計算したい場合は、関数型更新を使います。 これにより、連続して状態を更新しても正しく動作します。

🔄 直接値 vs 関数型更新の比較

📝 直接値で更新

setCount(count + 1);

現在の値: count = 5

計算: 5 + 1 = 6

新しい値: count = 6

⚠️ 連続して呼び出すと問題が発生する可能性

⚡ 関数型更新(推奨)

setCount(prev => prev + 1);

前の値を受け取る

計算: prev + 1

新しい値: count = 6

✅ 連続して呼び出しても安全

💡 違い: 直接値で更新する場合、複数回連続して呼び出すと古い値を使って計算してしまいます。 関数型更新では、常に最新の値(prev)を使って計算するため、連続呼び出しでも正しく動作します。

📝 関数型更新の例

import { useState } from "react"; const Counter = () => { const [count, setCount] = useState(0); // ❌ 問題がある例 const incrementTwiceWrong = () => { setCount(count + 1); // 0 + 1 = 1 setCount(count + 1); // 0 + 1 = 1 (同じ値) // 結果: count = 1 (期待: 2) }; // ✅ 正しい例(関数型更新) const incrementTwiceCorrect = () => { setCount(prev => prev + 1); // 0 + 1 = 1 setCount(prev => prev + 1); // 1 + 1 = 2 // 結果: count = 2 ✓ }; return ( <div> <p>カウント: {count}</p> <button onClick={incrementTwiceCorrect}> +2(関数型更新) </button> </div> ); };

✅ 関数型更新のメリット:

  • 連続して状態を更新しても正しく動作する
  • 最新の値を使って計算できる
  • 非同期処理でも安全

📚 身近な例えで理解しよう:銀行の残高の例

直接値: 「現在の残高は1000円です。+500円してください」と伝えると、 2回伝えても「1000 + 500 = 1500円」のままです。

関数型更新: 「現在の残高に+500円してください」と伝えると、 2回伝えると「1000 → 1500 → 2000円」と正しく増えます。

3. オブジェクトの状態更新

オブジェクトの状態を更新する時は、スプレッド構文(...)を使って、 新しいオブジェクトを作成する必要があります。

🔄 オブジェクト状態の更新パターン

間違った更新方法

// ❌ これは動きません!
user.name = "新しい名前";
setUser(user);

⚠️ 問題点:

  • Reactは同じオブジェクト参照を検知できない
  • 状態が更新されても再レンダリングされない
  • オブジェクトを直接変更(ミューテーション)してはいけない

方法1: スプレッド構文を使う(推奨)

// ✅ 新しいオブジェクトを作成
setUser({ ...user, name: "新しい名前" });
📦

古いuser

name: "旧"

age: 20

新しいuser

name: "新"

age: 20

...userで全ての値をコピー → nameだけを上書き

方法2: 関数型更新を使う

// ✅ 前の状態を使って更新
setUser(prev => ({ ...prev, name: "新しい名前" }));

💡 複数の更新を連続で行う場合に便利です

📦 身近な例えで理解しよう:引っ越しの例

❌ 間違った方法: 引っ越し先の住所を直接書き換えるのではなく、新しい住所が書かれた新しい書類を作る必要があります。 同じ書類を書き換えてしまうと、システムが「変更された」と認識できません。

✅ 正しい方法: 古い住所が書かれた書類をコピーして(...user)、住所だけを新しいものに書き換えます。 これで新しい書類として認識され、状態が正しく更新されます。

📝 オブジェクト状態の更新例

import { useState } from "react"; const UserProfile = () => { const [user, setUser] = useState({ name: "太郎", age: 25, email: "taro@example.com" }); // ✅ 正しい方法:スプレッド構文を使う const updateName = (newName) => { setUser({ ...user, name: newName }); }; // ✅ 複数のプロパティを更新 const updateProfile = (newName, newAge) => { setUser({ ...user, name: newName, age: newAge }); }; // ✅ 関数型更新を使う方法 const updateAge = () => { setUser(prev => ({ ...prev, age: prev.age + 1 })); }; return ( <div> <p>名前: {user.name}</p> <p>年齢: {user.age}</p> <button onClick={() => updateAge()}> 年齢を+1 </button> </div> ); };

4. 配列の状態更新

配列の状態を更新する時も、新しい配列を作成する必要があります。 追加、削除、更新の基本的なパターンを学びましょう。

📋 配列の状態更新パターン

➕ 要素を追加する

setItems([...items, newItem]);
AB
+
C
=
ABC

➖ 要素を削除する

setItems(items.filter(item => item.id !== id));
ABC
AC

✏️ 要素を更新する

setItems(items.map(item =>
  item.id === id ? {...item, ...updates} : item
));
ABC
AB'C

📝 配列操作の例

import { useState } from "react"; const TodoList = () => { const [items, setItems] = useState(["タスク1", "タスク2"]); // ✅ 要素を追加 const addItem = (newItem) => { setItems([...items, newItem]); // または setItems(items.concat(newItem)); }; // ✅ 要素を削除 const removeItem = (index) => { setItems(items.filter((_, i) => i !== index)); }; // ✅ 要素を更新 const updateItem = (index, newValue) => { setItems(items.map((item, i) => i === index ? newValue : item )); }; // ✅ 先頭に追加 const addToStart = (newItem) => { setItems([newItem, ...items]); }; return ( <div> {items.map((item, index) => ( <div key={index}>{item}</div> ))} </div> ); };

✅ 配列操作のベストプラクティス:

  • 追加: [...items, newItem] または items.concat(newItem)
  • 削除: items.filter(...)
  • 更新: items.map(...)

5. 条件付き更新

特定の条件が満たされた時だけ状態を更新したい場合もあります。 このような場合、条件分岐を使って更新を制御します。

📝 条件付き更新の例

import { useState } from "react"; const Counter = () => { const [count, setCount] = useState(0); // ✅ 最大値の制限 const incrementWithMax = () => { setCount(prev => { if (prev >= 10) { return prev; // 10以上なら更新しない } return prev + 1; }); }; // ✅ 最小値の制限 const decrementWithMin = () => { setCount(prev => prev > 0 ? prev - 1 : 0); }; // ✅ 条件に応じた更新 const updateConditionally = (newValue) => { if (newValue > 0 && newValue < 100) { setCount(newValue); } }; return ( <div> <p>カウント: {count}</p> <button onClick={incrementWithMax}>+1(最大10まで)</button> <button onClick={decrementWithMin}>-1(最小0まで)</button> </div> ); };

📚 まとめ

🎯 覚えておきたいポイント

  1. 直接値: シンプルな値(文字列、数値、真偽値)の更新に適している
  2. 関数型更新: 前の値を使う場合は必ず関数型更新を使う(連続更新でも安全)
  3. オブジェクト: スプレッド構文(...object)を使って新しいオブジェクトを作成する
  4. 配列: filtermapconcatなどを使って新しい配列を作成する
  5. 条件付き更新: 条件分岐を使って、必要な時だけ状態を更新する