状態管理完全ガイド
状態管理完全ガイド
Section titled “状態管理完全ガイド”状態管理の基礎から、実務で使える実装例とベストプラクティスまで詳しく解説します。
1. 状態管理とは
Section titled “1. 状態管理とは”状態管理の役割
Section titled “状態管理の役割”状態管理は、アプリケーションのデータとUIの状態を管理するための仕組みです。適切な状態管理により、アプリケーションの保守性と拡張性が向上します。
状態管理の目的 ├─ データの一元管理 ├─ コンポーネント間のデータ共有 ├─ 予測可能なデータフロー └─ デバッグの容易性なぜ状態管理が必要か
Section titled “なぜ状態管理が必要か”問題のある構成(状態管理なし):
// 問題: Props Drillingfunction App() { const [user, setUser] = useState(null); return <Layout user={user} setUser={setUser} />;}
function Layout({ user, setUser }) { return <Header user={user} setUser={setUser} />;}
function Header({ user, setUser }) { return <UserMenu user={user} setUser={setUser} />;}
function UserMenu({ user, setUser }) { // ようやく使用できる return <div>{user?.name}</div>;}
// 問題点:// 1. 中間コンポーネントが不要なpropsを受け取る// 2. コードが冗長になる// 3. リファクタリングが困難解決: 状態管理ライブラリの使用
// 解決: Zustandを使用import { create } from 'zustand';
const useUserStore = create((set) => ({ user: null, setUser: (user) => set({ user }),}));
// どのコンポーネントからでも直接アクセス可能function UserMenu() { const user = useUserStore((state) => state.user); return <div>{user?.name}</div>;}2. 状態の種類
Section titled “2. 状態の種類”ローカル状態
Section titled “ローカル状態”ローカル状態は、単一のコンポーネント内でのみ使用される状態です。
// useStateを使用したローカル状態function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>+</button> </div> );}
// useReducerを使用した複雑なローカル状態function Counter() { const [state, dispatch] = useReducer(reducer, { count: 0 }); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: 'increment' })}>+</button> </div> );}使用場面:
- フォーム入力
- UIの表示/非表示
- アニメーション状態
- コンポーネント固有の状態
グローバル状態
Section titled “グローバル状態”グローバル状態は、複数のコンポーネント間で共有される状態です。
// Zustandを使用したグローバル状態import { create } from 'zustand';
const useStore = create((set) => ({ count: 0, user: null, increment: () => set((state) => ({ count: state.count + 1 })), setUser: (user) => set({ user }),}));
// 使用function Counter() { const count = useStore((state) => state.count); const increment = useStore((state) => state.increment); return ( <div> <p>Count: {count}</p> <button onClick={increment}>+</button> </div> );}使用場面:
- ユーザー情報
- テーマ設定
- 認証状態
- アプリケーション全体の設定
サーバー状態
Section titled “サーバー状態”サーバー状態は、サーバーから取得したデータの状態です。
// TanStack Queryを使用したサーバー状態import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) { const { data, isLoading, error } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), });
if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>;
return <div>{data?.name}</div>;}使用場面:
- APIから取得したデータ
- キャッシュが必要なデータ
- リアルタイム更新が必要なデータ
- ページネーション
3. 状態管理の選択基準
Section titled “3. 状態管理の選択基準”状態の種類による選択
Section titled “状態の種類による選択”| 状態の種類 | 推奨ライブラリ | 理由 |
|---|---|---|
| ローカル状態 | useState、useReducer | シンプルで十分 |
| グローバル状態(小規模) | Context API、Zustand | 学習コストが低い |
| グローバル状態(中規模) | Zustand、Recoil、Jotai | バランスが良い |
| グローバル状態(大規模) | Redux、Zustand | 厳格な管理が必要 |
| サーバー状態(シンプル) | SWR | 軽量でシンプル |
| サーバー状態(複雑) | TanStack Query | 機能が豊富 |
プロジェクト規模による選択
Section titled “プロジェクト規模による選択”小規模プロジェクト
Section titled “小規模プロジェクト”// Context API + SWRimport { createContext, useContext } from 'react';import useSWR from 'swr';
// グローバル状態: Context APIconst ThemeContext = createContext();
// サーバー状態: SWRfunction UserList() { const { data } = useSWR('/api/users', fetcher); return <div>{data?.map(user => <div key={user.id}>{user.name}</div>)}</div>;}中規模プロジェクト
Section titled “中規模プロジェクト”// Zustand + TanStack Queryimport { create } from 'zustand';import { useQuery } from '@tanstack/react-query';
// グローバル状態: Zustandconst useAppStore = create((set) => ({ theme: 'light', setTheme: (theme) => set({ theme }),}));
// サーバー状態: TanStack Queryfunction UserList() { const { data } = useQuery({ queryKey: ['users'], queryFn: fetchUsers, }); return <div>{data?.map(user => <div key={user.id}>{user.name}</div>)}</div>;}大規模プロジェクト
Section titled “大規模プロジェクト”// Redux Toolkit + TanStack Queryimport { configureStore } from '@reduxjs/toolkit';import { useQuery } from '@tanstack/react-query';
// グローバル状態: Redux Toolkitexport const store = configureStore({ reducer: { // reducers },});
// サーバー状態: TanStack Queryfunction UserList() { const { data } = useQuery({ queryKey: ['users'], queryFn: fetchUsers, }); return <div>{data?.map(user => <div key={user.id}>{user.name}</div>)}</div>;}4. 実務でのベストプラクティス
Section titled “4. 実務でのベストプラクティス”パターン1: 状態の分離
Section titled “パターン1: 状態の分離”// 状態を適切に分離// グローバル状態: ユーザー情報const useUserStore = create((set) => ({ user: null, setUser: (user) => set({ user }),}));
// サーバー状態: 商品一覧function ProductList() { const { data } = useQuery({ queryKey: ['products'], queryFn: fetchProducts, }); return <div>{data?.map(product => <div key={product.id}>{product.name}</div>)}</div>;}
// ローカル状態: フォーム入力function ProductForm() { const [name, setName] = useState(''); return <input value={name} onChange={(e) => setName(e.target.value)} />;}パター2: 状態の正規化
Section titled “パター2: 状態の正規化”// 問題: 状態が正規化されていないconst useStore = create((set) => ({ users: [ { id: 1, name: 'John', posts: [{ id: 1, title: 'Post 1' }] }, { id: 2, name: 'Jane', posts: [{ id: 2, title: 'Post 2' }] }, ],}));
// 解決: 状態を正規化const useStore = create((set) => ({ users: { 1: { id: 1, name: 'John' }, 2: { id: 2, name: 'Jane' }, }, posts: { 1: { id: 1, userId: 1, title: 'Post 1' }, 2: { id: 2, userId: 2, title: 'Post 2' }, }, userPosts: { 1: [1], 2: [2], },}));5. よくある問題と解決策
Section titled “5. よくある問題と解決策”問題1: 不要な再レンダリング
Section titled “問題1: 不要な再レンダリング”原因:
- 状態の選択が不適切
- メモ化が不十分
解決策:
// Zustandを使用した細かい粒度での選択const useStore = create((set) => ({ count: 0, name: 'John', increment: () => set((state) => ({ count: state.count + 1 })),}));
// 細かい粒度での選択function Counter() { const count = useStore((state) => state.count); // nameが変更されても再レンダリングされない return <div>{count}</div>;}問題2: 状態の同期が取れない
Section titled “問題2: 状態の同期が取れない”原因:
- 状態の管理が分散している
- 単一の真実の源がない
解決策:
// 単一の真実の源を確保const useAppStore = create((set) => ({ user: null, setUser: (user) => set({ user }),}));
// すべてのコンポーネントで同じストアを使用function UserProfile() { const user = useAppStore((state) => state.user); return <div>{user?.name}</div>;}
function UserMenu() { const user = useAppStore((state) => state.user); return <div>{user?.email}</div>;}これで、状態管理の基礎知識と実務での使い方を理解できるようになりました。