状態管理 レッスン1
状態管理の基本
なぜ状態管理が必要なのか?問題と解決策を理解しよう
なぜ状態管理が必要か?
小さなReactアプリでは、useStateだけで十分に状態を管理できます。 しかし、アプリが大きくなると次のような問題が発生します。
- 複数のコンポーネントで同じデータを共有したい
- 状態の更新ロジックが複雑になる
- データの流れが追いにくくなる
- コンポーネントの再レンダリングが増えてパフォーマンスが低下する
// 小さなアプリ:useStateで十分
function SimpleApp() {
const [count, setCount] = useState(0);
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
// 大きなアプリ:状態の共有が複雑になる
// ユーザー情報、テーマ、通知、カートの中身...
// これらを複数のページ・コンポーネントで共有するには?Props Drilling問題
Props Drillingとは、深くネストされたコンポーネントにデータを渡すために、 中間のコンポーネントが不要なpropsをバケツリレーのように受け渡す問題です。
// Props Drilling の悪い例
// App → Layout → Sidebar → UserMenu → Avatar
// userデータが4階層も受け渡される
function App() {
const [user, setUser] = useState({ name: "太郎", avatar: "/img.png" });
return <Layout user={user} />; // 1階層目
}
function Layout({ user }) {
return (
<div>
<Sidebar user={user} /> {/* 2階層目(Layoutはuserを使わない) */}
<main>コンテンツ</main>
</div>
);
}
function Sidebar({ user }) {
return <UserMenu user={user} />; {/* 3階層目(Sidebarもuserを使わない) */}
}
function UserMenu({ user }) {
return <Avatar src={user.avatar} />; {/* 4階層目:やっと使う */}
}
function Avatar({ src }) {
return <img src={src} alt="avatar" />;
}LayoutやSidebarはuserを使わないのに、 下の階層に渡すためだけにpropsを受け取っています。これがProps Drillingです。
Lifting State Up(状態の引き上げ)
複数のコンポーネントで状態を共有する最も基本的な方法がLifting State Up(状態の引き上げ)です。 共通の親コンポーネントに状態を配置し、子コンポーネントにpropsとして渡します。
// 状態の引き上げの例
// 2つの入力フィールドの値を同期させる
function TemperatureConverter() {
// 共通の親で状態を管理
const [celsius, setCelsius] = useState(0);
const fahrenheit = celsius * 9 / 5 + 32;
return (
<div>
<TemperatureInput
label="摂氏(°C)"
value={celsius}
onChange={(val) => setCelsius(val)}
/>
<TemperatureInput
label="華氏(°F)"
value={fahrenheit}
onChange={(val) => setCelsius((val - 32) * 5 / 9)}
/>
</div>
);
}
function TemperatureInput({ label, value, onChange }) {
return (
<label>
{label}:
<input
type="number"
value={value}
onChange={(e) => onChange(Number(e.target.value))}
/>
</label>
);
}状態の引き上げはシンプルですが、コンポーネント階層が深くなるとProps Drillingにつながります。
グローバル状態が必要なとき
すべての状態をグローバルにする必要はありません。 以下の基準でローカル状態とグローバル状態を使い分けましょう。
// ローカル状態(useState)で十分なケース
// - フォームの入力値
// - モーダルの開閉状態
// - ドロップダウンの選択値
// - アコーディオンの展開状態
function SearchBar() {
const [query, setQuery] = useState(""); // ローカルで十分
return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}
// グローバル状態が必要なケース
// - ログインユーザー情報(多くのコンポーネントで参照)
// - テーマ設定(ダーク/ライトモード)
// - 言語設定(i18n)
// - ショッピングカートの中身
// - 通知の一覧
// 判断基準:
// 1. 複数の離れたコンポーネントが同じデータを必要とする
// 2. ページ遷移しても状態を保持したい
// 3. Props Drillingが3階層以上になる状態管理ソリューションの全体像
Reactの状態管理には多くの選択肢があります。それぞれの特徴を把握しましょう。
// 1. React組み込み
// useState → コンポーネントローカルの状態
// useReducer → 複雑なローカル状態
// useContext → 軽量なグローバル状態
// 2. 外部ライブラリ(中〜大規模向け)
// Redux Toolkit → 最も採用実績が多い、強力なDevTools
// Zustand → 軽量でシンプル、ボイラープレート少ない
// Jotai → アトミックな状態管理、React的な設計
// Recoil → Meta製、実験的(開発停滞中)
// 3. サーバー状態管理
// TanStack Query(React Query) → API通信の状態管理
// SWR → Vercel製、シンプル
// RTK Query → Redux Toolkitの一部
// 選び方の目安:
// 小規模アプリ → useState + useContext
// 中規模アプリ → Zustand or Jotai
// 大規模・チーム開発 → Redux Toolkit
// API通信がメイン → TanStack Query + 軽量な状態管理まとめ
- アプリが大きくなると
useStateだけでは管理が難しくなる - Props Drillingは中間コンポーネントが不要なpropsを受け渡す問題
- Lifting State Upは状態共有の基本だが、深い階層では限界がある
- グローバル状態は「複数の離れたコンポーネントが同じデータを必要とする」場合に使う
- useContext、Redux Toolkit、Zustand、Jotaiなど目的に応じた選択肢がある