レッスン一覧に戻る

複数の状態管理

レッスン 4

複数のuseStateを使ったり、オブジェクトで状態をまとめて管理する方法を学びます

推定学習時間: 25分

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

実践的なアプリケーションでは、1つのコンポーネントで複数の状態を管理する必要があります。 このレッスンでは、複数の状態を効率的に管理する方法を学びます。

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

  • 個別のuseState: それぞれの状態を独立して管理する方法
  • オブジェクトのuseState: 関連する状態をまとめて管理する方法
  • 状態の更新パターン: オブジェクト状態を正しく更新する方法
  • 使い分けの判断基準: いつ個別の状態を使い、いつオブジェクトを使うか

1. 個別のuseStateを使う方法

最もシンプルな方法は、それぞれの状態に対して個別にuseStateを使うことです。 この方法は、状態が互いに独立している場合に最適です。

✅ 個別のuseStateが適している場合

  • 状態が互いに独立している
  • 状態を個別に更新することが多い
  • コードがシンプルで読みやすい

📝 例:ユーザー情報フォーム

import { useState } from "react"; const UserForm = () => { // 個別のuseStateで管理 const [name, setName] = useState(""); const [age, setAge] = useState(0); const [email, setEmail] = useState(""); return ( <form> <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="名前" /> <input type="number" value={age} onChange={(e) => setAge(Number(e.target.value))} placeholder="年齢" /> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="メールアドレス" /> </form> ); };

✅ メリット:

  • 各状態が独立していて、更新が明確
  • 一つの状態を更新しても他の状態に影響しない
  • コードが読みやすく、理解しやすい

📸 実世界での状態管理のイメージ

✅ 個別のuseState = 個別の引き出し

name"太郎""太郎"age2525email"taro@..."taro@...それぞれ独立した引き出し1つを開けても他の引き出しは影響なし

📦 オブジェクトのuseState = 整理箱

username: "太郎"age: 25email: "taro@..."1つの箱に全てをまとめて保管中身を変更する時は新しい箱を作る

💡 理解のポイント: 個別のuseStateは「それぞれ専用の引き出し」で、オブジェクトのuseStateは「1つの整理箱」のようなものです。 引き出しは1つを開けても他に影響しませんが、整理箱の中身を変える時は新しい箱を作る必要があります。

🔄 状態更新のフロー比較

✅ 個別のuseState

1

ユーザーが入力

name フィールドに "太郎" と入力

2

setName実行

setName("太郎")

3

nameだけ更新

age, emailは影響なし

4

再レンダリング

nameだけが変更される

📦 オブジェクトのuseState

1

ユーザーが入力

name フィールドに "太郎" と入力

2

新しいオブジェクト作成

...user で全てコピー

3

nameだけ上書き

age, emailはそのまま

4

setUser実行

新しいuserオブジェクトで更新

⌨️ フォーム入力時の状態変化の様子

1

初期状態

name

""

age

0

email

""

ユーザーがまだ何も入力していない状態

2

nameフィールドに "太郎" を入力

name

"太郎" ✨

更新された

age

0

変更なし

email

""

変更なし

setName("太郎") が実行され、nameだけが更新される

3

ageフィールドに 25 を入力

name

"太郎"

保持される

age

25 ✨

更新された

email

""

変更なし

setAge(25) が実行され、ageだけが更新される(nameはそのまま保持)

💡 ポイント: 個別のuseStateでは、1つの状態を更新しても他の状態には影響しません。 フォームの入力フィールドが独立して動作する様子が分かります。

📚 身近な例えで理解しよう:引き出しの例

個別のuseStateは、「名前用の引き出し」「年齢用の引き出し」「メール用の引き出し」を それぞれ用意するようなものです。それぞれの引き出しに物を入れたり出したりするのが 独立していて、他の引き出しに影響しません。

2. オブジェクトのuseStateを使う方法

関連する状態を1つのオブジェクトにまとめて管理する方法もあります。 この方法は、状態が関連している場合や、まとめて扱うことが多い場合に便利です。

📦 複数の状態を管理する方法の比較

方法1: 個別のuseState(推奨)

const [name, setName] = useState("");
const [age, setAge] = useState(0);
const [email, setEmail] = useState("");
👤

name

独立した状態

🎂

age

独立した状態

📧

email

独立した状態

✅ メリット:

  • 各状態が独立していて、更新が明確
  • 一つの状態を更新しても他の状態に影響しない
  • コードが読みやすい
📦

方法2: オブジェクトのuseState

const [user, setUser] = useState({
  name: "",
  age: 0,
  email: ""
});
📦

user オブジェクト

name

age

email

📝 使用例:

  • 関連するデータをまとめて管理したい時
  • フォームの入力値をまとめて扱いたい時
  • 更新時にスプレッド構文(...)を使う必要がある

🌳 状態の構造を視覚化

✅ 個別のuseStateの構造

Componentname"太郎"age25email"taro@..."✓ 各状態が独立✓ フラットな構造

📦 オブジェクトのuseStateの構造

Componentusernameageemail✓ 階層的な構造✓ 関連データをまとめる

💡 構造の違い: 個別のuseStateは「フラットな構造」で、各状態が同じ階層に並んでいます。 一方、オブジェクトのuseStateは「階層的な構造」で、userという親オブジェクトの中に各プロパティが入っています。

📦 オブジェクトのuseStateが適している場合

  • 状態が互いに関連している
  • 状態をまとめて初期化・リセットすることが多い
  • 状態を一つの単位として扱いたい

📝 例:ユーザー情報をオブジェクトで管理

import { useState } from "react"; const UserForm = () => { // オブジェクトのuseStateで管理 const [user, setUser] = useState({ name: "", age: 0, email: "" }); return ( <form> <input type="text" value={user.name} onChange={(e) => setUser({ ...user, name: e.target.value }) } placeholder="名前" /> <input type="number" value={user.age} onChange={(e) => setUser({ ...user, age: Number(e.target.value) }) } placeholder="年齢" /> <input type="email" value={user.email} onChange={(e) => setUser({ ...user, email: e.target.value }) } placeholder="メールアドレス" /> </form> ); };

✅ メリット:

  • 関連するデータを一つの単位として扱える
  • 状態をまとめてリセットしたり、初期化しやすい
  • APIから取得したデータ構造と一致させやすい

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

オブジェクトの状態を更新する時は、新しいオブジェクトを作成する必要があります。 Reactは、同じオブジェクト参照を検知できないため、直接変更しても再レンダリングされません。

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

間違った更新方法

// ❌ これは動きません!
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)、住所だけを新しいものに書き換えます。 これで新しい書類として認識され、状態が正しく更新されます。

⚠️ 重要なポイント:イミュータビリティ(不変性)

Reactでは、状態を直接変更(ミューテーション)してはいけません。 必ず新しいオブジェクトや配列を作成して、状態を更新する必要があります。 これがReactの「イミュータビリティ(不変性)」という原則です。

📝 複数のプロパティを一度に更新する例

const handleSubmit = () => { // スプレッド構文で全ての値をコピーして、必要な部分だけ更新 setUser({ ...user, name: "新しい名前", age: 25 }); // または関数型更新を使う setUser((prev) => ({ ...prev, name: "新しい名前", age: 25 })); };

4. 使い分けの判断基準

個別のuseStateとオブジェクトのuseState、どちらを使うべきか判断する基準を学びましょう。

✅ 個別のuseStateを使う場合

  • 状態が互いに独立している
  • 状態を個別に更新することが多い
  • シンプルなコンポーネント
  • 状態の数が少ない(2〜3個程度)

📦 オブジェクトのuseStateを使う場合

  • 状態が互いに関連している
  • 状態をまとめて扱うことが多い
  • APIのデータ構造と一致させたい
  • 状態を一括でリセットしたい

💡 実践的なアドバイス

最初は個別のuseStateから始めることをおすすめします。 コードがシンプルで理解しやすく、ほとんどの場合で十分です。 状態が増えてきたり、関連性が強くなってきたら、オブジェクトのuseStateに移行することを検討しましょう。

📚 まとめ

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

  1. 個別のuseStateは、独立した状態を管理するのに最適です
  2. オブジェクトのuseStateは、関連する状態をまとめて管理するのに便利です
  3. オブジェクト状態を更新する時は、必ず新しいオブジェクトを作成します(スプレッド構文を使う)
  4. 状態を直接変更(ミューテーション)してはいけません
  5. 最初は個別のuseStateから始めて、必要に応じてオブジェクトに移行しましょう