こんにちは、かつコーチです。
ここまで3回にわたって、useReducer、Context API、Redux Toolkit、Zustandと、状態管理まわりの選択肢を一つずつ解説してきました。
「結局、自分のプロジェクトではどれを使えばいいの?」というのが、この一連の記事を読んだ人が一番気になるところだと思います。
今日は、Redux Toolkit・Zustand・Context APIの3つを横並びで比較し、実際にどう選べばいいのかという判断軸を整理していきます。
なぜ状態管理ライブラリが複数存在するのか
それぞれ生まれた背景が違う
まず前提として、この3つは「同じことができる別の道具」ではなく、それぞれ違う課題感から生まれてきたものです。
- Context API:Reactに標準で組み込まれた、props drillingを解決するための仕組み
- Redux:大規模アプリで「状態がいつ・なぜ変わったか」を厳密に追跡するために生まれ、Redux Toolkitがその使いやすさを大幅に改善した
- Zustand:Reduxの手厚さを保ちつつ、もっと少ないコードで状態管理をしたいというニーズから生まれた
この背景の違いが、そのまま「得意な場面の違い」につながっています。
比較の前提:状態管理は「必要な場所」にだけ使う
比較に入る前に大事な前提を1つ確認しておきます。
すべての状態をグローバルに管理する必要はありません。
フォームの入力値やモーダルの開閉状態のような「そのコンポーネント内で完結する状態」は、useState や useReducer で十分です。
今回比較するのは、あくまで「複数のコンポーネントで共有する必要があるグローバルな状態」をどう管理するか、という話だという点を押さえておいてください。
3つの選択肢を徹底比較
機能・設計面の比較表
| 比較項目 | Context API | Redux Toolkit | Zustand |
|---|---|---|---|
| 導入のしやすさ | Reactに標準搭載、追加インストール不要 | ライブラリのインストールが必要 | ライブラリのインストールが必要 |
| Providerの設置 | 必要 | 必要 | 不要 |
| ボイラープレート | 少ない | Redux Toolkitで大幅に削減されたが、それでも slice や store の定義が必要 | 非常に少ない(create 1つ) |
| 非同期処理 | 自前で実装する必要あり | createAsyncThunk / RTK Queryで標準化されている | 通常の非同期関数として書ける |
| DevTools(状態の変化を追跡する開発者ツール) | 標準では非対応 | 標準で強力に対応 | ミドルウェア追加で対応 |
| 再レンダリングの最適化 | useMemo / useCallback を自分で意識する必要あり | useSelector で自動的に最適化される | セレクターで自動的に最適化される |
| 学習コスト | 低い(Reactの基本機能の延長) | やや高い(Action・Reducer・Sliceの概念) | 低い(シンプルなAPI) |
| 向いている規模 | 小〜中規模 | 中〜大規模、チーム開発 | 小〜中規模、個人〜小チーム |
パフォーマンス面の違い
Context APIは、Providerの value が変わるたびに、そのContextを使っているすべてのコンポーネントが再レンダリング対象になります。
前々回解説した通り、useMemo や useCallback で value の参照を安定させる工夫が必要で、この最適化を怠るとアプリの規模が大きくなったときにパフォーマンス問題が起きやすくなります。
一方、Redux ToolkitとZustandは、どちらも「セレクター」という仕組みで、コンポーネントが使う値だけを監視し、その値が変わったときだけ再レンダリングする設計になっています。
状態の種類・更新頻度が多いアプリほど、この違いが体感できるレベルの差になってきます。
結局どれを選べばいいのか:判断軸
判断フロー
私自身の経験も踏まえて、次のような判断軸で選ぶことをおすすめします。
- 状態がそのコンポーネント(または近い親子関係)だけで完結するか?
→ YesならuseState/useReducerで十分。グローバル状態管理そのものが不要 - 共有したい状態は少数で、更新頻度もそれほど高くないか?(テーマ、ログイン中のユーザー情報など)
→ Context APIで十分。Reactの標準機能だけで完結する手軽さが強み - チーム開発で、状態変化の追跡・デバッグのしやすさを重視するか?
→ Redux Toolkit。DevToolsでの状態追跡や、大規模開発での型・ルールの統一に強い - 導入の手軽さとコード量の少なさを優先したいか?
→ Zustand。Providerも不要で、小〜中規模のアプリなら最速で状態管理を導入できる
具体的なシーン別のおすすめ
判断軸をさらに具体的なシーンに落とし込むと、次のようになります。
- 個人開発・小規模なSaaSの管理画面:Zustand(学習コストの低さとコード量の少なさが効いてくる)
- 社内向けの小さなダッシュボード:Context API(追加ライブラリなしで完結できる手軽さが向いている)
- ECサイトなど、複数チームが関わる大規模なプロダクト:Redux Toolkit(状態変化の追跡性、ルールの統一のしやすさが効いてくる)
- 既存プロジェクトにReduxがすでに導入されている場合:基本的にはRedux Toolkitに寄せる(新旧の状態管理方法が混在すると保守性が下がるため)
つまずきやすいポイント:「とりあえずRedux」で選んでしまう
かつコーチが実際にハマった話
私が過去に関わった小規模な個人開発プロジェクトで、「有名だから」という理由だけでRedux Toolkitを導入してしまい、slice の設計や store の構成を考える時間の方が、実際の機能実装よりも長くかかってしまったことがあります。
管理する状態は「ログイン中のユーザー情報」と「テーマ」の2つだけだったので、今振り返るとContext APIか、当時はまだ普及していませんでしたが今ならZustandで十分だったと感じています。
❌ Before:規模を考えずに知名度だけでRedux Toolkitを選ぶ
// 管理したい状態はuserとthemeの2つだけなのに、
// sliceを2つ、storeの設定、Providerの配置まで一式必要になる
const userSlice = createSlice({ name: "user", initialState, reducers: { /* ... */ } });
const themeSlice = createSlice({ name: "theme", initialState, reducers: { /* ... */ } });
export const store = configureStore({
reducer: { user: userSlice.reducer, theme: themeSlice.reducer },
});
✅ After:規模に見合った選択肢(この場合はZustand)を選ぶ
// createひとつで、userとthemeの2つの状態をまとめて定義できる
const useAppStore = create<{
user: { name: string } | null;
theme: "light" | "dark";
setUser: (user: { name: string } | null) => void;
toggleTheme: () => void;
}>((set, get) => ({
user: null,
theme: "light",
setUser: (user) => set({ user }),
toggleTheme: () => set({ theme: get().theme === "light" ? "dark" : "light" }),
}));
管理したい状態の量とチームの規模に対して、道具の重さが釣り合っているかを最初に確認する習慣がついてからは、こうした「導入コストの方が実装より重い」という事態は起きなくなりました。
「有名だから」「みんな使っているから」ではなく、自分のプロジェクトの規模に合わせて選ぶことが何より大切です。
まとめ
この記事のポイント
- Context API・Redux Toolkit・Zustandは、それぞれ違う課題感から生まれた別の道具であり、優劣ではなく適材適所で選ぶ
- 小規模でシンプルに済ませたいならContext API、大規模・複雑な状態やチーム開発ならRedux Toolkit、シンプルさとコード量の少なさを重視するならZustand
- どの道具を選ぶ前に、そもそもグローバルな状態管理が必要かどうか(
useState/useReducerで足りないか)を確認する - パフォーマンス面では、Redux ToolkitとZustandはセレクターによる自動最適化がある一方、Context APIは
useMemo/useCallbackによる手動の工夫が必要 - 「有名だから」ではなく、プロジェクトの規模とチーム体制に見合った道具を選ぶことが重要
次に読むべき記事
状態管理の全体像を押さえたところで、次はいよいよAPI連携の実践に入っていきます。
→ 次の記事:fetchでAPIからデータを取得して表示する