状態管理.mdx
📚 Reactの基礎について: 基本的なReactの概念(useState、useEffect、コンポーネント、Props、Context APIなど)については、Reactガイドを参照してください。このドキュメントでは、Next.jsでの状態管理に焦点を当てます。
⚛️ 状態管理とは
Section titled “⚛️ 状態管理とは”📚 Reactの基礎について: 基本的なReactの概念(useState、useEffect、コンポーネント、Propsなど)については、Reactガイドを参照してください。このドキュメントでは、Next.jsでの状態管理に焦点を当てます。
状態管理は、アプリケーションのデータを一元的に管理し、複数のコンポーネント間で共有・更新する仕組みです。これにより、コンポーネントツリーを深く辿ってデータを渡す**「Props Drilling」**の問題が解決され、アプリケーションのデータフローが予測可能になります。
なぜ状態管理が重要なのか
Section titled “なぜ状態管理が重要なのか”Props Drillingの問題
Section titled “Props Drillingの問題”📚 Props Drillingの詳細: Props Drillingの問題とContext APIによる解決方法については、ReactガイドのContext APIを参照してください。
問題のあるコード:
// 問題: Propsを何層にも渡す必要がある// 注意: useStateの詳細については、Reactガイドを参照してくださいfunction App() { const [user, setUser] = useState(null);
return <Layout user={user} setUser={setUser} />;}
function Layout({ user, setUser }: { user: User | null; setUser: (user: User) => void }) { return <Header user={user} setUser={setUser} />;}
function Header({ user, setUser }: { user: User | null; setUser: (user: User) => void }) { return <UserMenu user={user} setUser={setUser} />;}
function UserMenu({ user, setUser }: { user: User | null; setUser: (user: User) => void }) { // ようやく使用できる return <div>{user?.name}</div>;}
// 問題点:// - 中間コンポーネントが不要なpropsを受け取る// - コードが冗長になる// - 型定義が複雑になる// - リファクタリングが困難解決: 状態管理ライブラリの使用
// 解決: Zustandを使用import { create } from 'zustand';
interface UserStore { user: User | null; setUser: (user: User) => void;}
const useUserStore = create<UserStore>((set) => ({ user: null, setUser: (user) => set({ user }),}));
// どのコンポーネントからでも直接アクセス可能function UserMenu() { const user = useUserStore((state) => state.user); return <div>{user?.name}</div>;}状態管理の本質的な価値
Section titled “状態管理の本質的な価値”1. データの単一の真実の源(Single Source of Truth)
📚 useStateの詳細: useStateの詳細については、ReactガイドのuseStateとuseEffectを参照してください。
// 問題: 状態が複数の場所に散在// 注意: useStateの詳細については、Reactガイドを参照してくださいfunction ComponentA() { const [cart, setCart] = useState([]); // ...}
function ComponentB() { const [cart, setCart] = useState([]); // 同じ状態が重複 // ...}
// 解決: グローバルストアで一元管理const useCartStore = create((set) => ({ items: [], addItem: (item) => set((state) => ({ items: [...state.items, item] })),}));
// すべてのコンポーネントが同じ状態を参照function ComponentA() { const items = useCartStore((state) => state.items); // ...}
function ComponentB() { const items = useCartStore((state) => state.items); // 同じ状態 // ...}2. 予測可能なデータフロー
📚 useEffectの詳細: useEffectの詳細については、ReactガイドのuseStateとuseEffectを参照してください。
// 問題: 状態の更新が予測困難// 注意: useStateとuseEffectの詳細については、Reactガイドを参照してくださいfunction Component() { const [data, setData] = useState(null);
useEffect(() => { // 複数の場所で状態を更新 fetchData1().then(setData); fetchData2().then(setData); // 競合状態の可能性 }, []);}
// 解決: 一元化された更新ロジックconst useDataStore = create((set) => ({ data: null, fetchData: async () => { const data1 = await fetchData1(); const data2 = await fetchData2(); set({ data: { ...data1, ...data2 } }); // 予測可能な更新 },}));3. パフォーマンス最適化
// 問題: 不要な再レンダリングfunction ExpensiveComponent({ user }: { user: User }) { // userが変更されると再レンダリング return <ExpensiveRender user={user} />;}
// 解決: 必要な部分だけを購読function ExpensiveComponent() { // user.nameだけを購読(userオブジェクト全体の変更には反応しない) const userName = useUserStore((state) => state.user?.name); return <ExpensiveRender userName={userName} />;}1. Zustand
Section titled “1. Zustand”Zustandは、シンプルでミニマリストな状態管理ライブラリです。フックAPIをベースにしており、ボイラープレートが非常に少ないのが特徴です。
- 特徴: 少ないコードでグローバルストアを作成でき、
useStoreフックで必要な状態だけを簡単に取得できます。
2. Redux
Section titled “2. Redux”Reduxは、大規模で複雑なアプリケーション向けの状態管理ライブラリです。**「単一方向データフロー」**に基づき、厳格なルールでデータの変更を管理します。
- 特徴: 状態の変更をアクションとリデューサーを通じて行うため、予測可能性が高く、デバッグが容易です。ただし、設定が複雑になりがちですが、Redux Toolkitで簡素化できます。
3. Recoil
Section titled “3. Recoil”Recoilは、Reactの思想に近い状態管理ライブラリで、状態を**「Atom」**という小さな単位に分割して管理します。
- 特徴:
useStateに似た感覚で使え、Atom(最小単位の状態)とSelector(Atomから派生した状態)を通じて、必要な状態だけを効率的に購読できます。
4. Jotai
Section titled “4. Jotai”Jotai(ジョタイ)は、日本語の「状態」から名付けられたライブラリで、Recoilと同様にアトミックな状態管理を採用しています。
- 特徴: RecoilよりもさらにミニマリストなAPIを提供し、
useStateフックに非常に近い感覚でグローバル状態を扱えます。ボトムアップで小さなAtomを組み合わせて複雑な状態を構築するのに適しています。
どのライブラリを選ぶべきか?
Section titled “どのライブラリを選ぶべきか?”| 比較項目 | Zustand | Redux | Recoil | Jotai |
|---|---|---|---|---|
| 学習コスト | 非常に低い | 高い | 低い | 非常に低い |
| ボイラープレート | 非常に少ない | 多い | 少ない | 少ない |
| 規模 | 小〜中規模 | 大規模 | 小〜大規模 | 小〜大規模 |
| コンセプト | フックベース | 単一方向データフロー | Atom & Selector | Atom & ボトムアップ |
-
シンプルさ: ZustandとJotaiは、直感的に使い始めたい場合に最適です。
-
大規模開発: Reduxは、厳格なルールと予測可能性が求められる大規模なエンタープライズアプリケーションに向いています。
-
Reactとの親和性: RecoilとJotaiは、アトミックなアプローチにより、コンポーネントベースのReact開発と相性が良いです。特にJotaiは、よりミニマルなAPIを好む開発者におすすめです。
状態管理の三権分立
Section titled “状態管理の三権分立”状態管理を理解する際、以下の3つの役割を「どこに配置するか」を明確にすることが重要です。それぞれの役割を理解することで、適切な状態管理の選択ができるようになります。
1. Local State(ローカル状態): その場限りの使い捨て
Section titled “1. Local State(ローカル状態): その場限りの使い捨て”📚 useStateの詳細: useStateの詳細な使い方については、ReactガイドのuseStateとuseEffectを参照してください。
役割: useStateを使用したコンポーネント内での状態管理
特徴:
- そのコンポーネント内でのみ使用される状態
- コンポーネントがアンマウントされると、状態も破棄される
- 最もシンプルで、パフォーマンスへの影響が最小限
使用例:
// 注意: useStateの詳細については、Reactガイドを参照してくださいimport { useState } from 'react';
function Counter() { // Local State: このコンポーネント内でのみ使用 const [count, setCount] = useState(0); const [isOpen, setIsOpen] = useState(false);
return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> <button onClick={() => setIsOpen(!isOpen)}>Toggle</button> </div> );}判断基準:
- 1つのコンポーネント内でのみ使用される
- 他のコンポーネントと共有する必要がない
- Props Drillingが2層以内で済む場合は、Local Stateで十分
2. Context API: 「依存性の注入」。状態そのものより、値を奥深くまで届けるパイプライン
Section titled “2. Context API: 「依存性の注入」。状態そのものより、値を奥深くまで届けるパイプライン”📚 Context APIの詳細: Context APIの詳細な使い方(作成、提供、消費の3ステップ、注意点など)については、ReactガイドのContext APIを参照してください。
役割: コンポーネントツリー全体で値を共有するための仕組み
特徴:
- 状態の「伝達」に特化した仕組み
- 状態を保持するためには、
useStateやuseReducerが必要 - Contextの値が更新されると、そのContextを使用している全コンポーネントが再レンダリングされる
使用例:
📚 Context APIの詳細: Context APIの詳細な使い方(作成、提供、消費の3ステップ、注意点など)については、ReactガイドのContext APIを参照してください。
// 注意: Context API、useState、useContextの詳細については、Reactガイドを参照してくださいimport { createContext, useContext, useState, ReactNode } from 'react';
type Theme = 'light' | 'dark';
interface ThemeContextType { theme: Theme; setTheme: (theme: Theme) => void;}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export function ThemeProvider({ children }: { children: ReactNode }) { // 状態の保持にはuseStateが必要 const [theme, setTheme] = useState<Theme>('light');
return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> );}
export function useTheme() { const context = useContext(ThemeContext); if (context === undefined) { throw new Error('useTheme must be used within a ThemeProvider'); } return context;}3. Server State(サーバー状態): サーバーの影(キャッシュ)。「今、手元にあるデータは最新か?」を管理する専門職
Section titled “3. Server State(サーバー状態): サーバーの影(キャッシュ)。「今、手元にあるデータは最新か?」を管理する専門職”役割: サーバーからのデータをキャッシュし、再取得や同期を管理
特徴:
- APIから取得したデータの管理
- キャッシュ、再取得、同期が自動的に行われる
- TanStack QueryやSWRなどの専用ライブラリが必要
使用例:
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }: { userId: string }) { // Server State: サーバーからのデータを管理 const { data: user, isLoading, error } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), staleTime: 5 * 60 * 1000, // 5分間は新鮮とみなす });
if (isLoading) return <div>読み込み中...</div>; if (error) return <div>エラーが発生しました</div>;
return <div>{user.name}</div>;}判断基準:
- サーバーから取得したデータ
- キャッシュが必要
- 再取得が必要
- 同期が必要
useContextのまさかりポイント:それは「状態管理」ではない
Section titled “useContextのまさかりポイント:それは「状態管理」ではない”📚 Context APIの詳細: Context APIの詳細な説明については、ReactガイドのContext APIを参照してください。
よくある誤解ですが、Context APIは単体では「状態管理ライブラリ」ではありません。
💡 まさかりポイント
Section titled “💡 まさかりポイント”Contextは「値を伝達する仕組み」に過ぎません。 状態の保持には結局useStateやuseReducerが必要です。
// ❌ 誤解: Contextが状態を保持している// 注意: Context APIの詳細については、Reactガイドを参照してくださいconst ThemeContext = createContext(null); // これは値の伝達の仕組みのみ
// ✅ 正解: useStateが状態を保持している// 注意: useStateの詳細については、Reactガイドを参照してくださいexport function ThemeProvider({ children }: { children: ReactNode }) { const [theme, setTheme] = useState('light'); // ここで状態を保持 return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> );}注意点: Contextの再レンダリング問題
Section titled “注意点: Contextの再レンダリング問題”📚 Context APIの注意点: Context APIの詳細な注意点(再レンダリングの問題など)については、ReactガイドのContext APIを参照してください。
Contextの値が更新されると、そのContextを使っている(消費している)全コンポーネントが再レンダリングされるため、頻繁に更新されるグローバルな状態(例:タイマーや入力値)には不向きです。
// ❌ バッドプラクティス: 頻繁に更新される状態をContextで管理// 注意: useStateとuseEffectの詳細については、Reactガイドを参照してくださいexport function TimerProvider({ children }: { children: ReactNode }) { const [time, setTime] = useState(new Date());
useEffect(() => { const interval = setInterval(() => { setTime(new Date()); // 1秒ごとに更新 }, 1000); return () => clearInterval(interval); }, []);
return ( <TimerContext.Provider value={{ time }}> {children} {/* 問題: 1秒ごとに全コンポーネントが再レンダリングされる */} </TimerContext.Provider> );}
// ✅ ベストプラクティス: 頻繁に更新されない状態のみContextで管理// context/ThemeContext.tsx// 注意: useStateの詳細については、Reactガイドを参照してくださいexport function ThemeProvider({ children }: { children: ReactNode }) { const [theme, setTheme] = useState('light'); // めったに更新されない
return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> );}💡 シニアのアドバイス
Section titled “💡 シニアのアドバイス”「Contextは、認証情報、テーマ、言語設定など、めったに変わらない『静的な共有データ』に限定して使いましょう。」
適切な使用例:
- ✅ 認証情報(ユーザー情報、ログイン状態)
- ✅ テーマ(ライト/ダークモード)
- ✅ 言語設定(i18n)
- ✅ ルーティング情報
不適切な使用例:
- ❌ タイマー(頻繁に更新される)
- ❌ フォームの入力値(頻繁に更新される)
- ❌ アニメーションの状態(頻繁に更新される)
- ❌ サーバーから取得したデータ(TanStack QueryやSWRを使用すべき)
SWR vs TanStack Query (Server State)
Section titled “SWR vs TanStack Query (Server State)”これらは「データを取ってくるツール」ではなく、**「サーバーの状態をクライアントに同期させるキャッシュマネージャー」**です。
TanStack Query(旧React Query)の強み(SWRとの比較)
Section titled “TanStack Query(旧React Query)の強み(SWRとの比較)”1. 宣言的なデータ取得
Section titled “1. 宣言的なデータ取得”TanStack Queryの思想:
「いつ取るか」ではなく「このキー(['user', id])のデータが欲しい」と宣言するだけです。
// ✅ TanStack Query: 宣言的なデータ取得import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }: { userId: string }) { // 「user」と「userId」のキーでデータが欲しいと宣言 const { data: user } = useQuery({ queryKey: ['user', userId], // キャッシュのキー queryFn: () => fetchUser(userId), // データ取得関数 staleTime: 5 * 60 * 1000, // 5分間は新鮮 });
return <div>{user?.name}</div>;}
// ✅ SWR: 同様に宣言的import useSWR from 'swr';
function UserProfile({ userId }: { userId: string }) { // 「user」と「userId」のキーでデータが欲しいと宣言 const { data: user } = useSWR(`/api/users/${userId}`, fetcher);
return <div>{user?.name}</div>;}2. 自動再取得(Window Focus Refetching)
Section titled “2. 自動再取得(Window Focus Refetching)”ブラウザのタブを切り替えて戻ってきた瞬間に、裏側でデータを最新にする機能です。これはSWRにもありますが、TanStack Queryの方が細かい制御(Mutationの成功後に特定のキーを無効化するなど)に長けています。
// ✅ TanStack Query: 細かい制御が可能import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function UserProfile({ userId }: { userId: string }) { const queryClient = useQueryClient();
// ユーザー情報を取得 const { data: user } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), staleTime: 5 * 60 * 1000, refetchOnWindowFocus: true, // ウィンドウフォーカス時に再取得 });
// ユーザー情報を更新 const updateUser = useMutation({ mutationFn: (data: UserData) => updateUser(userId, data), onSuccess: () => { // 更新成功後、特定のキーを無効化(再取得をトリガー) queryClient.invalidateQueries({ queryKey: ['user', userId] }); // または、関連するすべてのキーを無効化 queryClient.invalidateQueries({ queryKey: ['users'] }); }, });
return ( <div> <div>{user?.name}</div> <button onClick={() => updateUser.mutate({ name: '新しい名前' })}> 更新 </button> </div> );}3. キャッシュ戦略の制御
Section titled “3. キャッシュ戦略の制御”TanStack Queryは、より細かいキャッシュ戦略の制御が可能です。
// ✅ TanStack Query: 詳細なキャッシュ制御import { useQuery } from '@tanstack/react-query';
function ProductList() { const { data: products } = useQuery({ queryKey: ['products'], queryFn: () => fetchProducts(), staleTime: 5 * 60 * 1000, // 5分間は新鮮とみなす gcTime: 10 * 60 * 1000, // 10分間キャッシュを保持(旧cacheTime) refetchOnMount: true, // マウント時に再取得 refetchOnWindowFocus: true, // ウィンドウフォーカス時に再取得 refetchOnReconnect: true, // ネットワーク再接続時に再取得 });
return <div>{/* ... */}</div>;}比較表: TanStack Query vs SWR
Section titled “比較表: TanStack Query vs SWR”| 機能 | TanStack Query | SWR |
|---|---|---|
| 宣言的なデータ取得 | ✅ | ✅ |
| 自動再取得 | ✅ | ✅ |
| キャッシュ制御 | ✅(詳細) | ✅(シンプル) |
| Mutation後の無効化 | ✅(柔軟) | ✅(限定的) |
| DevTools | ✅(優秀) | ❌ |
| バンドルサイズ | 約13KB | 約4KB |
| 学習コスト | 中 | 低 |
| TypeScriptサポート | ✅(優秀) | ✅ |
どちらを選ぶべきか?
Section titled “どちらを選ぶべきか?”TanStack Queryを選ぶべき場合:
- 複雑なキャッシュ戦略が必要
- Mutationの制御が重要
- DevToolsでデバッグしたい
- 大規模なアプリケーション
SWRを選ぶべき場合:
- シンプルで軽量な解決策が必要
- バンドルサイズを最小限に抑えたい
- 学習コストを低く抑えたい
- 小〜中規模のアプリケーション
状態管理ライブラリの選択判断
Section titled “状態管理ライブラリの選択判断”サーバー状態 vs クライアント状態
Section titled “サーバー状態 vs クライアント状態”重要な区別:
// サーバー状態: APIから取得したデータ// - キャッシュが必要// - 再取得が必要// - 同期が必要// → React Query / SWR が適している
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }: { userId: string }) { const { data, isLoading, error } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), staleTime: 5 * 60 * 1000, // 5分間は新鮮 cacheTime: 10 * 60 * 1000, // 10分間キャッシュ });
if (isLoading) return <Loading />; if (error) return <Error />; return <div>{data.name}</div>;}
// クライアント状態: UIの状態// - モーダルの開閉// - フォームの入力値// - グローバルなUI状態// → Zustand / Jotai / Redux が適している
import { create } from 'zustand';
interface UIStore { isModalOpen: boolean; openModal: () => void; closeModal: () => void;}
const useUIStore = create<UIStore>((set) => ({ isModalOpen: false, openModal: () => set({ isModalOpen: true }), closeModal: () => set({ isModalOpen: false }),}));状態管理ライブラリの詳細比較
Section titled “状態管理ライブラリの詳細比較”Zustandの適用範囲:
// Zustandが適している場合:// 1. シンプルなグローバル状態// 2. ミドルウェアが不要// 3. 学習コストを抑えたい
import { create } from 'zustand';import { devtools, persist } from 'zustand/middleware';
interface CartStore { items: CartItem[]; addItem: (item: CartItem) => void; removeItem: (id: string) => void;}
const useCartStore = create<CartStore>()( devtools( persist( (set) => ({ items: [], addItem: (item) => set((state) => ({ items: [...state.items, item] })), removeItem: (id) => set((state) => ({ items: state.items.filter(item => item.id !== id) })), }), { name: 'cart-storage' } // localStorageに永続化 ) ));
// メリット:// - シンプルで理解しやすい// - ボイラープレートが少ない// - TypeScriptとの統合が良い
// デメリット:// - 複雑な状態遷移には不向き// - タイムトラベルデバッグがないReduxの適用範囲:
// Reduxが適している場合:// 1. 複雑な状態遷移// 2. タイムトラベルデバッグが必要// 3. 厳格な状態管理が必要// 4. 大規模なエンタープライズアプリケーション
import { createSlice, configureStore } from '@reduxjs/toolkit';
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); }, },});
export const store = configureStore({ reducer: { cart: cartSlice.reducer, },});
// メリット:// - 予測可能な状態管理// - 優秀なデバッグツール// - ミドルウェアのエコシステム
// デメリット:// - 学習コストが高い// - ボイラープレートが多い// - 小規模プロジェクトには過剰Jotaiの適用範囲:
// Jotaiが適している場合:// 1. アトミックな状態管理// 2. 細かい粒度での最適化が必要// 3. Reactの思想に近い状態管理
import { atom, useAtom } from 'jotai';
// アトミックな状態const userAtom = atom<User | null>(null);const cartAtom = atom<CartItem[]>([]);
// 派生状態const cartCountAtom = atom((get) => get(cartAtom).length);
function CartButton() { const [cart] = useAtom(cartAtom); const count = useAtomValue(cartCountAtom); // カウントだけを購読
return <button>Cart ({count})</button>;}
// メリット:// - 非常に細かい粒度での最適化// - useStateに近い感覚// - ボトムアップで状態を構築
// デメリット:// - 複雑な状態遷移には不向き// - エコシステムが小さい実践的な選択指針
Section titled “実践的な選択指針”プロジェクト規模による選択:
// 小規模プロジェクト(< 50コンポーネント)// → Zustand + React Query// 理由: シンプル、学習コストが低い、十分な機能
// 中規模プロジェクト(50-200コンポーネント)// → Zustand + React Query + Context API// 理由: 機能ごとに状態を分離、グローバル状態はZustand
// 大規模プロジェクト(200+コンポーネント)// → Redux Toolkit + React Query// 理由: 厳格な状態管理、タイムトラベルデバッグ、複雑な状態遷移状態の種類による選択:
// サーバー状態 → React Queryconst { data } = useQuery({ queryKey: ['users'], queryFn: fetchUsers,});
// UI状態 → Zustandconst useUIStore = create((set) => ({ isModalOpen: false, toggleModal: () => set((state) => ({ isModalOpen: !state.isModalOpen })),}));
// フォーム状態 → React Hook Form(状態管理ライブラリではないが)const { register, handleSubmit } = useForm();
// グローバルなビジネスロジック → Redux / Zustandconst useOrderStore = create((set) => ({ orders: [], createOrder: async (orderData) => { const order = await createOrderAPI(orderData); set((state) => ({ orders: [...state.orders, order] })); },}));状態管理のアンチパターン
Section titled “状態管理のアンチパターン”よくある間違い
Section titled “よくある間違い”間違い1: すべての状態をグローバルストアに配置
// 悪い例: ローカル状態をグローバルストアにconst useFormStore = create((set) => ({ inputValue: '', // 問題: 1つのコンポーネントでしか使用しない setInputValue: (value: string) => set({ inputValue: value }),}));
// 良い例: ローカル状態はuseStateを使用// 注意: useStateの詳細については、Reactガイドを参照してくださいfunction Form() { const [inputValue, setInputValue] = useState(''); // ローカル状態 return <input value={inputValue} onChange={(e) => setInputValue(e.target.value)} />;}間違い2: サーバー状態をクライアント状態管理ライブラリで管理
// 悪い例: Zustandでサーバー状態を管理const useUsersStore = create((set) => ({ users: [], fetchUsers: async () => { const users = await fetch('/api/users').then(res => res.json()); set({ users }); },}));
// 問題:// - キャッシュ機能がない// - 再取得のロジックが複雑// - エラーハンドリングが不十分
// 良い例: React Queryでサーバー状態を管理const { data: users } = useQuery({ queryKey: ['users'], queryFn: () => fetch('/api/users').then(res => res.json()), staleTime: 5 * 60 * 1000,});間違い3: Props Drillingを過度に避ける
📚 Context APIの詳細: Context APIの詳細な使い方については、ReactガイドのContext APIを参照してください。
// 悪い例: 2-3層のProps Drillingを避けるためにグローバルストアを使用// 親 → 子 → 孫 の3層だけなのにグローバルストアを使用
// 良い例: Context APIで十分な場合// 注意: Context APIの詳細については、Reactガイドを参照してくださいconst UserContext = createContext<User | null>(null);
function App() { const [user, setUser] = useState<User | null>(null); return ( <UserContext.Provider value={user}> <Layout /> </UserContext.Provider> );}
// 判断基準:// - 2-3層のProps Drilling → Context API// - 4層以上、または複数のブランチ → 状態管理ライブラリ状態管理の深い理解において重要なポイント:
- 状態の種類を理解: サーバー状態 vs クライアント状態
- 適切なライブラリの選択: プロジェクト規模と要件に応じて
- 状態の配置判断: グローバル vs ローカル vs Context
- アンチパターンの回避: 過度なグローバル化を避ける
シニアエンジニアとして考慮すべき点:
- 状態のスコープ: 状態が必要な範囲を正確に判断
- パフォーマンス: 不要な再レンダリングを避ける
- 保守性: 長期的に保守可能な状態管理設計
- チームの合意: チーム全体で統一された状態管理戦略