メインコンテンツにスキップ

🎛️ 状態管理とは?

📖 定義

**状態(State)**はアプリケーションのデータとUI状態を意味し、状態管理はこのような状態を効率的に保存、更新、共有する方法です。Reactで複数のコンポーネントが同じデータを使用する時、状態管理ライブラリを通じて複雑性を減らし、コードをきれいに維持することができます。

🎯 比喩で理解する

銀行口座の比喩

状態管理を銀行システムに例えると:

個人財布 (Component State)
├─ 本人のみ使用
├─ 簡単で速い
└─ 他の人と共有不可

家族共同口座 (Context API)
├─ 家族全員がアクセス
├─ 設定簡単
└─ 家族が多くなると複雑

銀行システム (Redux/Zustand)
├─ 中央集中式管理
├─ すべての取引記録
├─ 複雑だが体系的
└─ 監査(audit)可能

例:
ユーザーログイン情報
├─ ヘッダー: ユーザー名表示
├─ サイドバー: プロフィール写真
├─ 設定: メール情報
└─ フッター: ログアウトボタン

すべてのコンポーネントが同じユーザー情報が必要
→ 状態管理で一箇所で管理!

放送局の比喩

地方放送 (Local State)
- 一つのコンポーネント内のみ
- useStateを使用
例: モーダル開閉状態

中央放送 (Global State)
- アプリ全体からアクセス
- Redux/Zustandを使用
例: ログインユーザー情報

ニュース速報 (State Updates)
- 中央から発信
- すべての購読者に配信
- 自動UI更新

リモコン (Actions)
- チャンネル変更リクエスト
- ボリューム調整リクエスト
- 明確なインターフェース

図書館の比喩

机上メモ (Local State)
- 個人作業スペース
- 速くて簡単
- 他の人が見れない

図書館掲示板 (Context API)
- 同じフロアの利用者が共有
- 中規模
- 設定簡単

中央データベース (Redux)
- 図書館全体のシステム
- すべての本の位置を追跡
- 貸出記録管理
- 体系的だが複雑

リアルタイム通知 (Reactive State)
- 本が返却されると自動通知
- 購読者にのみ送信
- Recoil/Jotai方式

⚙️ 動作原理

1. Props Drilling問題

// ❌ Props Drilling (地獄の階段)

// App.js
function App() {
const [user, setUser] = useState({ name: '田中太郎', email: 'tanaka@example.com' });

return <Layout user={user} setUser={setUser} />;
}

// Layout.js
function Layout({ user, setUser }) {
return (
<div>
<Header user={user} setUser={setUser} />
<Sidebar user={user} />
<Main user={user} setUser={setUser} />
</div>
);
}

// Header.js
function Header({ user, setUser }) {
return <UserMenu user={user} setUser={setUser} />;
}

// UserMenu.js
function UserMenu({ user, setUser }) {
return <ProfileButton user={user} setUser={setUser} />;
}

// ProfileButton.js
function ProfileButton({ user, setUser }) {
// ようやく使用!
return (
<button onClick={() => setUser({ ...user, name: '佐藤花子' })}>
{user.name}
</button>
);
}

// 問題点:
// 1. userを4段階も伝達 (App → Layout → Header → UserMenu → ProfileButton)
// 2. 中間コンポーネントが不要にpropsを受け取る
// 3. props追加/修正時にすべてのコンポーネントを修正
// 4. コード複雑度増加
// 5. メンテナンス困難

2. 状態管理の解決策

// ✅ 状態管理ライブラリを使用

// store.js (中央ストア)
const store = {
user: { name: '田中太郎', email: 'tanaka@example.com' }
};

// App.js
function App() {
return (
<div>
<Header />
<Sidebar />
<Main />
</div>
);
// props伝達不要!
}

// ProfileButton.js
function ProfileButton() {
// 必要なコンポーネントのみ直接アクセス
const user = useStore(state => state.user);
const updateUser = useStore(state => state.updateUser);

return (
<button onClick={() => updateUser({ name: '佐藤花子' })}>
{user.name}
</button>
);
}

// 利点:
// 1. props伝達不要
// 2. 中間コンポーネントへの影響なし
// 3. コードが簡潔
// 4. メンテナンスしやすい
// 5. パフォーマンス最適化可能

3. 単方向データフロー

// 状態管理の基本原則

┌─────────────────────────────────────┐
Store (ストア)
{ count: 0, user: {...}, ... }
└─────────────────────────────────────┘
購読(subscribe)

┌─────────────────────────────────────┐
Component (コンポーネント)
UIレンダリング: <div>{count}</div>
└─────────────────────────────────────┘
イベント (: ボタンクリック)

┌─────────────────────────────────────┐
Action (アクション)
{ type: 'INCREMENT' }
└─────────────────────────────────────┘
伝達(dispatch)

┌─────────────────────────────────────┐
Reducer (リデューサー)
│ count = count + 1
└─────────────────────────────────────┘
↓ 状態更新

再びStoreへ ↰

// 特徴:
// 1. 予測可能: 同じ入力 → 同じ出力
// 2. 追跡可能: すべての変更記録
// 3. デバッグしやすい: タイムトラベル可能
// 4. テストしやすい: 純粋関数

💡 実際の例

Context API (基本)

// 1. Context生成
import { createContext, useContext, useState } from 'react';

const UserContext = createContext();

// 2. Providerコンポーネント
function UserProvider({ children }) {
const [user, setUser] = useState({
name: '田中太郎',
email: 'tanaka@example.com',
isLoggedIn: false
});

const login = (email, password) => {
// API呼び出し
setUser({
name: '田中太郎',
email: email,
isLoggedIn: true
});
};

const logout = () => {
setUser({
name: '',
email: '',
isLoggedIn: false
});
};

return (
<UserContext.Provider value={{ user, login, logout }}>
{children}
</UserContext.Provider>
);
}

// 3. Custom Hook
function useUser() {
const context = useContext(UserContext);
if (!context) {
throw new Error('useUser must be used within UserProvider');
}
return context;
}

// 4. AppでProviderで囲む
function App() {
return (
<UserProvider>
<Header />
<Main />
<Footer />
</UserProvider>
);
}

// 5. 使用
function Header() {
const { user, logout } = useUser();

return (
<header>
<h1>こんにちは、{user.name}さん</h1>
{user.isLoggedIn && (
<button onClick={logout}>ログアウト</button>
)}
</header>
);
}

function LoginForm() {
const { login } = useUser();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleSubmit = (e) => {
e.preventDefault();
login(email, password);
};

return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">ログイン</button>
</form>
);
}

// Context APIの長所と短所:
// 長所:
// - 追加ライブラリ不要
// - 簡単な設定
// - React内蔵機能

// 短所:
// - パフォーマンス最適化が困難
// - Provider入れ子地獄
// - DevToolsなし

Redux (複雑だが強力)

// 1. Store設定
// store.js
import { configureStore, createSlice } from '@reduxjs/toolkit';

// Slice生成 (状態 + リデューサー)
const userSlice = createSlice({
name: 'user',
initialState: {
name: '',
email: '',
isLoggedIn: false,
loading: false,
error: null
},
reducers: {
loginStart: (state) => {
state.loading = true;
state.error = null;
},
loginSuccess: (state, action) => {
state.name = action.payload.name;
state.email = action.payload.email;
state.isLoggedIn = true;
state.loading = false;
},
loginFailure: (state, action) => {
state.loading = false;
state.error = action.payload;
},
logout: (state) => {
state.name = '';
state.email = '';
state.isLoggedIn = false;
}
}
});

// Actions export
export const { loginStart, loginSuccess, loginFailure, logout } = userSlice.actions;

// Thunk (非同期アクション)
export const loginAsync = (email, password) => async (dispatch) => {
dispatch(loginStart());
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await response.json();
dispatch(loginSuccess(data));
} catch (error) {
dispatch(loginFailure(error.message));
}
};

// Store生成
const store = configureStore({
reducer: {
user: userSlice.reducer
}
});

export default store;

// 2. AppにProvider接続
// index.js
import { Provider } from 'react-redux';
import store from './store';

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);

// 3. コンポーネントで使用
// LoginForm.js
import { useDispatch, useSelector } from 'react-redux';
import { loginAsync } from './store';

function LoginForm() {
const dispatch = useDispatch();
const { loading, error } = useSelector(state => state.user);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleSubmit = (e) => {
e.preventDefault();
dispatch(loginAsync(email, password));
};

return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={loading}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={loading}
/>
<button type="submit" disabled={loading}>
{loading ? 'ログイン中...' : 'ログイン'}
</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
</form>
);
}

// Header.js
import { useDispatch, useSelector } from 'react-redux';
import { logout } from './store';

function Header() {
const dispatch = useDispatch();
const { name, isLoggedIn } = useSelector(state => state.user);

return (
<header>
{isLoggedIn && (
<>
<span>こんにちは、{name}さん</span>
<button onClick={() => dispatch(logout())}>
ログアウト
</button>
</>
)}
</header>
);
}

// Reduxの長所と短所:
// 長所:
// - 強力なDevTools (タイムトラベルデバッグ)
// - ミドルウェア (ロギング、非同期処理)
// - 巨大なエコシステム
// - 予測可能な状態管理
// - 厳格な構造

// 短所:
// - ボイラープレートが多い
// - 学習曲線が高い
// - 簡単なアプリには過剰

Zustand (シンプルで現代的)

// 1. Store生成
// store.js
import create from 'zustand';

const useStore = create((set, get) => ({
// 状態
user: {
name: '',
email: '',
isLoggedIn: false
},
loading: false,
error: null,

// アクション
login: async (email, password) => {
set({ loading: true, error: null });
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await response.json();
set({
user: {
name: data.name,
email: data.email,
isLoggedIn: true
},
loading: false
});
} catch (error) {
set({ error: error.message, loading: false });
}
},

logout: () => {
set({
user: {
name: '',
email: '',
isLoggedIn: false
}
});
},

// 派生状態 (computed)
getFullName: () => {
const { user } = get();
return user.name || 'ゲスト';
}
}));

export default useStore;

// 2. コンポーネントで使用 (Provider不要!)
// LoginForm.js
import useStore from './store';

function LoginForm() {
const { login, loading, error } = useStore();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleSubmit = (e) => {
e.preventDefault();
login(email, password);
};

return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button disabled={loading}>
{loading ? 'ログイン中...' : 'ログイン'}
</button>
{error && <p>{error}</p>}
</form>
);
}

// Header.js
import useStore from './store';

function Header() {
// 必要な状態のみ選択 (パフォーマンス最適化)
const user = useStore(state => state.user);
const logout = useStore(state => state.logout);

return (
<header>
{user.isLoggedIn && (
<>
<span>こんにちは、{user.name}さん</span>
<button onClick={logout}>ログアウト</button>
</>
)}
</header>
);
}

// 3. ミドルウェア使用
import create from 'zustand';
import { persist } from 'zustand/middleware';

// localStorageに自動保存
const useStore = create(
persist(
(set) => ({
user: { name: '', email: '', isLoggedIn: false },
login: async (email, password) => {
// ...
}
}),
{
name: 'user-storage' // localStorage key
}
)
);

// Zustandの長所と短所:
// 長所:
// - 非常にシンプルなAPI
// - Provider不要
// - TypeScript完全サポート
// - 小さいバンドルサイズ (1KB)
// - Redux DevToolsサポート
// - ミドルウェアサポート

// 短所:
// - エコシステムが小さい
// - 複雑なアプリでは?
// - 公式ドキュメント不足

Recoil (React親和的)

// 1. Atoms (状態単位)
// atoms.js
import { atom, selector } from 'recoil';

// Atom: 状態の断片
export const userState = atom({
key: 'userState',
default: {
name: '',
email: '',
isLoggedIn: false
}
});

export const loadingState = atom({
key: 'loadingState',
default: false
});

// Selector: 派生状態
export const userNameState = selector({
key: 'userNameState',
get: ({ get }) => {
const user = get(userState);
return user.name || 'ゲスト';
}
});

// 非同期Selector
export const userDataQuery = selector({
key: 'userDataQuery',
get: async ({ get }) => {
const response = await fetch('/api/user');
return response.json();
}
});

// 2. AppにRecoilRoot追加
// App.js
import { RecoilRoot } from 'recoil';

function App() {
return (
<RecoilRoot>
<Header />
<Main />
<Footer />
</RecoilRoot>
);
}

// 3. コンポーネントで使用
// LoginForm.js
import { useRecoilState, useSetRecoilState } from 'recoil';
import { userState, loadingState } from './atoms';

function LoginForm() {
const [user, setUser] = useRecoilState(userState);
const setLoading = useSetRecoilState(loadingState);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleLogin = async (e) => {
e.preventDefault();
setLoading(true);
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await response.json();
setUser({
name: data.name,
email: data.email,
isLoggedIn: true
});
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};

return (
<form onSubmit={handleLogin}>
{/* ... */}
</form>
);
}

// Header.js
import { useRecoilValue } from 'recoil';
import { userNameState } from './atoms';

function Header() {
const userName = useRecoilValue(userNameState);

return (
<header>
<span>こんにちは、{userName}さん</span>
</header>
);
}

// 4. 非同期データ
import { useRecoilValueLoadable } from 'recoil';
import { userDataQuery } from './atoms';

function UserProfile() {
const userLoadable = useRecoilValueLoadable(userDataQuery);

switch (userLoadable.state) {
case 'loading':
return <div>ロード中...</div>;
case 'hasError':
return <div>エラー: {userLoadable.contents.message}</div>;
case 'hasValue':
return <div>{userLoadable.contents.name}</div>;
}
}

// Recoilの長所と短所:
// 長所:
// - React Hooksスタイル
// - 非同期処理が簡単
// - 派生状態の自動計算
// - Concurrent Modeサポート
// - Facebook製作

// 短所:
// - まだ実験段階
// - 安定性への懸念
// - エコシステムが小さい
// - DevTools不足

比較例: ショッピングカート

// Context API バージョン
const CartContext = createContext();

function CartProvider({ children }) {
const [items, setItems] = useState([]);

const addItem = (item) => {
setItems([...items, item]);
};

const removeItem = (id) => {
setItems(items.filter(item => item.id !== id));
};

const total = items.reduce((sum, item) => sum + item.price, 0);

return (
<CartContext.Provider value={{ items, addItem, removeItem, total }}>
{children}
</CartContext.Provider>
);
}

// Redux バージョン
const cartSlice = createSlice({
name: 'cart',
initialState: { items: [] },
reducers: {
addItem: (state, action) => {
state.items.push(action.payload);
},
removeItem: (state, action) => {
state.items = state.items.filter(item => item.id !== action.payload);
}
}
});

// Zustand バージョン
const useCartStore = create((set) => ({
items: [],
addItem: (item) => set((state) => ({
items: [...state.items, item]
})),
removeItem: (id) => set((state) => ({
items: state.items.filter(item => item.id !== id)
})),
total: () => {
const { items } = get();
return items.reduce((sum, item) => sum + item.price, 0);
}
}));

// Recoil バージョン
const cartState = atom({
key: 'cartState',
default: []
});

const cartTotalSelector = selector({
key: 'cartTotalSelector',
get: ({ get }) => {
const cart = get(cartState);
return cart.reduce((sum, item) => sum + item.price, 0);
}
});

// 使用
function ShoppingCart() {
// Context
const { items, removeItem, total } = useContext(CartContext);

// Redux
const items = useSelector(state => state.cart.items);
const dispatch = useDispatch();

// Zustand
const { items, removeItem, total } = useCartStore();

// Recoil
const [items, setItems] = useRecoilState(cartState);
const total = useRecoilValue(cartTotalSelector);

return (
<div>
{items.map(item => (
<div key={item.id}>
{item.name} - {item.price}
<button onClick={() => removeItem(item.id)}>削除</button>
</div>
))}
<p>合計: {total}</p>
</div>
);
}

パフォーマンス最適化

// 1. Context API パフォーマンス問題
function BadContext() {
const [user, setUser] = useState({ name: '', age: 0 });
const [theme, setTheme] = useState('light');

// 問題: themeだけ変わってもuserを使用するコンポーネントすべて再レンダリング
return (
<AppContext.Provider value={{ user, setUser, theme, setTheme }}>
{children}
</AppContext.Provider>
);
}

// ✅ 解決: Context分離
function GoodContext() {
return (
<UserContext.Provider value={{ user, setUser }}>
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
</UserContext.Provider>
);
}

// 2. Redux セレクター最適化
// ❌ 悪い例
function BadComponent() {
const state = useSelector(state => state); // 全体状態!
return <div>{state.user.name}</div>;
}

// ✅ 良い例
function GoodComponent() {
const userName = useSelector(state => state.user.name); // 必要なものだけ
return <div>{userName}</div>;
}

// 3. Zustand セレクター最適化
// ❌ 悪い例
function BadComponent() {
const store = useStore(); // 全体ストア
return <div>{store.user.name}</div>;
}

// ✅ 良い例
function GoodComponent() {
const userName = useStore(state => state.user.name); // 必要なものだけ
return <div>{userName}</div>;
}

// 4. Recoil atomFamily (動的状態)
const userItemState = atomFamily({
key: 'UserItem',
default: id => fetch(`/api/users/${id}`).then(r => r.json())
});

function UserProfile({ userId }) {
const user = useRecoilValue(userItemState(userId));
return <div>{user.name}</div>;
}

// 5. メモ化
import { memo } from 'react';

const ExpensiveComponent = memo(function ExpensiveComponent({ value }) {
console.log('レンダリング:', value);
return <div>{value}</div>;
});

// valueが同じなら再レンダリングしない!

🤔 よくある質問

Q1. どの状態管理ライブラリを選択すべきですか?

A: プロジェクト規模と要求事項によって異なります:

// 選択ガイド

// Context API - シンプルなアプリ
const contextUseCases = {
適している場合: [
'小さなプロジェクト',
'状態共有が少ない',
'追加ライブラリを使いたくない',
'学習目的'
],
: [
'テーマ (ダーク/ライトモード)',
'言語設定',
'シンプルな認証状態',
'モーダル状態'
],
長所: '別途インストール不要',
短所: 'パフォーマンス最適化が困難'
};

// Redux - 大規模で複雑なアプリ
const reduxUseCases = {
適している場合: [
'大規模プロジェクト',
'複雑な状態ロジック',
'タイムトラベルデバッグが必要',
'厳格な構造を求める',
'チームプロジェクト'
],
: [
'企業向けダッシュボード',
'複雑なeコマース',
'金融アプリケーション',
'SaaSプラットフォーム'
],
長所: '強力なDevTools、エコシステム',
短所: '学習曲線、ボイラープレート'
};

// Zustand - 中規模、現代的
const zustandUseCases = {
適している場合: [
'中規模プロジェクト',
'シンプルなAPIを好む',
'速い開発',
'TypeScript使用'
],
: [
'一般的なWebアプリ',
'MVP開発',
'スタートアッププロジェクト',
'プロトタイプ'
],
長所: 'シンプル、小さいバンドル',
短所: 'エコシステムが小さい'
};

// Recoil - React中心、非同期
const recoilUseCases = {
適している場合: [
'Reactスタイルを好む',
'非同期データが多い',
'派生状態が複雑',
'実験的プロジェクト'
],
: [
'リアルタイムコラボレーションツール',
'データビジュアライゼーション',
'複雑なフォーム',
'GraphQLアプリ'
],
長所: 'Reactらしい、非同期が簡単',
短所: 'まだ実験段階'
};

// Jotai - Recoil代替
const jotaiUseCases = {
適している場合: [
'Recoilが欲しいが安定性が必要',
'アトミックな状態',
'小さいバンドルサイズ'
],
長所: 'シンプル、安定的',
短所: 'エコシステムが小さい'
};

// 意思決定ツリー
function chooseStateManagement(project) {
if (project.size === 'small') {
return 'Context API';
}

if (project.size === 'large' && project.needsStructure) {
return 'Redux Toolkit';
}

if (project.needsSimplicity && project.size === 'medium') {
return 'Zustand';
}

if (project.hasAsyncData && project.likesReactStyle) {
return 'Recoil or Jotai';
}

return 'Zustand'; // ほとんどの場合無難
}

Q2. すべての状態をグローバルで管理すべきですか?

A: いいえ、必要なものだけをグローバルで管理してください:

// 状態分類

// 1. Local State (コンポーネント内部)
function SearchBox() {
const [query, setQuery] = useState(''); // ローカル状態
const [isFocused, setIsFocused] = useState(false);

// 他のコンポーネントが必要としない状態
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
/>
);
}

// 2. Lifted State (親に持ち上げる)
function Parent() {
const [count, setCount] = useState(0); // 複数の子が共有

return (
<div>
<ChildA count={count} />
<ChildB setCount={setCount} />
</div>
);
}

// 3. Global State (グローバル状態)
// - 多くのコンポーネントがアクセス
// - アプリ全体に影響
const globalStates = {
user: 'ログインユーザー情報',
theme: 'ダーク/ライトモード',
language: '言語設定',
cart: 'ショッピングカート',
notifications: '通知リスト'
};

// 4. Server State (サーバーデータ)
// React Query、SWR使用推奨
function UserProfile() {
const { data, error, isLoading } = useQuery('user', fetchUser);

if (isLoading) return <div>ロード中...</div>;
if (error) return <div>エラー!</div>;
return <div>{data.name}</div>;
}

// 原則:
// - 基本はLocal State
// - 2-3個のコンポーネント共有 → Lifted State
// - 多くのコンポーネント共有 → Global State
// - APIデータ → Server State (React Query/SWR)

Q3. Redux ToolkitとReduxの違いは?

A: Redux Toolkitは、Reduxを簡単に使えるようにした公式ツールです:

// 既存Redux (複雑)
// actionTypes.js
const INCREMENT = 'counter/increment';
const DECREMENT = 'counter/decrement';

// actions.js
function increment() {
return { type: INCREMENT };
}

function decrement() {
return { type: DECREMENT };
}

// reducer.js
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case INCREMENT:
return { ...state, value: state.value + 1 };
case DECREMENT:
return { ...state, value: state.value - 1 };
default:
return state;
}
}

// store.js
import { createStore } from 'redux';
const store = createStore(counterReducer);

// Redux Toolkit (シンプル)
// counterSlice.js
import { createSlice, configureStore } from '@reduxjs/toolkit';

const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1; // Immerで不変性を自動処理!
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});

// Redux Toolkitの長所:
// 1. ボイラープレートが大幅に減少
// 2. Immerで不変性を自動処理
// 3. Redux DevToolsが自動設定
// 4. createAsyncThunkで非同期が簡単
// 5. TypeScriptサポートが優秀

// 非同期処理比較
// 既存Redux (redux-thunk)
function fetchUser(id) {
return async (dispatch) => {
dispatch({ type: 'user/fetchStart' });
try {
const user = await api.fetchUser(id);
dispatch({ type: 'user/fetchSuccess', payload: user });
} catch (error) {
dispatch({ type: 'user/fetchFailure', payload: error });
}
};
}

// Redux Toolkit (createAsyncThunk)
const fetchUser = createAsyncThunk(
'user/fetch',
async (id) => {
const user = await api.fetchUser(id);
return user;
}
);

const userSlice = createSlice({
name: 'user',
initialState: { data: null, loading: false, error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
}
});

// 結論: 新しいプロジェクトはRedux Toolkitを使用!

Q4. React Query/SWRと状態管理ライブラリの違いは?

A: 異なる目的で使用されます:

// 状態管理ライブラリ (Redux、Zustandなど)
// 目的: クライアント状態管理
const clientState = {
user: { name: '田中太郎', isLoggedIn: true }, // ログイン状態
theme: 'dark', // UI設定
cart: [{ id: 1, name: '商品' }], // ショッピングカート
modal: { isOpen: false } // UI状態
};

// React Query/SWR
// 目的: サーバー状態管理 (キャッシング、同期、再検証)
import { useQuery, useMutation } from 'react-query';

function UserProfile() {
// 自動キャッシング、再検証、ローディング状態管理
const { data, isLoading, error } = useQuery('user', fetchUser);

const mutation = useMutation(updateUser, {
onSuccess: () => {
// キャッシュ無効化
queryClient.invalidateQueries('user');
}
});

return (
<div>
{isLoading && <div>ロード中...</div>}
{error && <div>エラー: {error.message}</div>}
{data && <div>{data.name}</div>}
<button onClick={() => mutation.mutate({ name: '新しい名前' })}>
修正
</button>
</div>
);
}

// 一緒に使用する (推奨!)
// store.js (Zustand)
const useStore = create((set) => ({
theme: 'light',
setTheme: (theme) => set({ theme }),
// クライアント状態のみ
}));

// UserProfile.js
function UserProfile() {
// サーバー状態: React Query
const { data: user } = useQuery('user', fetchUser);

// クライアント状態: Zustand
const theme = useStore(state => state.theme);

return (
<div className={theme}>
{user.name}
</div>
);
}

// 比較:
// Redux/Zustand:
// - クライアント状態
// - 直接管理
// - 同期的

// React Query/SWR:
// - サーバー状態
// - 自動キャッシング/同期
// - 非同期的
// - バックグラウンド再検証
// - 楽観的更新

// 結論:
// 両方使用すると最高!
// - React Query: APIデータ
// - Zustand: UI状態、設定

Q5. 状態管理なしで開発できますか?

A: はい、シンプルなアプリはuseStateとpropsで十分です:

// 状態管理ライブラリなしで開発

// 1. 小さなアプリ
function SmallApp() {
const [count, setCount] = useState(0);
const [user, setUser] = useState(null);

// propsで伝達
return (
<div>
<Header user={user} />
<Counter count={count} setCount={setCount} />
</div>
);
}

// 2. Custom Hooksでロジック分離
function useUser() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);

const login = async (email, password) => {
setLoading(true);
const user = await api.login(email, password);
setUser(user);
setLoading(false);
};

const logout = () => {
setUser(null);
};

return { user, loading, login, logout };
}

function App() {
const { user, login, logout } = useUser();

return (
<div>
<Header user={user} logout={logout} />
<LoginForm login={login} />
</div>
);
}

// 3. React Queryのみ使用
function App() {
const { data: user } = useQuery('user', fetchUser);
const { data: posts } = useQuery('posts', fetchPosts);

return (
<div>
<Header user={user} />
<PostList posts={posts} />
</div>
);
}

// 状態管理ライブラリが必要になる時は?
const needsStateManagement = {
signs: [
'props伝達が3段階以上',
'複数のコンポーネントが同じ状態を使用',
'状態更新ロジックが複雑',
'グローバル設定が必要 (テーマ、言語)',
'デバッグが困難'
]
};

// 結論:
// - 開始はシンプルに (useState + props)
// - 複雑になったらContext API
// - さらに複雑になったらZustand/Redux
// - APIデータはReact Query/SWR

🎓 次のステップ

状態管理を理解したら、次を学習してみてください:

  1. Reactとは? (ドキュメント作成予定) - React基礎復習
  2. Virtual DOMとは? - Reactレンダリング原理
  3. Webパフォーマンス最適化 (ドキュメント作成予定) - 状態管理パフォーマンス最適化

🎬 まとめ

状態管理は現代Web開発の核心です:

  • Context API: シンプルなアプリ、React内蔵
  • Redux: 大規模アプリ、強力なツール
  • Zustand: 中規模、シンプルなAPI
  • Recoil: Reactスタイル、非同期が便利

プロジェクトに合ったツールを選択し、必要な状態のみグローバルで管理してください!