React開発における状態管理のスケーラブルな構造を考えてみる
React開発における状態管理のスケーラブルな構造とは 「拡張性があり、保守しやすく、チームやプロダクトの成長に対応できる構成」のとして考えます。
小さなアプリケーションでは useState や useContext で事足りることもありますが、機能が増え、開発メンバーが増えてくると、それでは破綻してしまいます。
本記事では、Reactアプリケーションにおける状態管理のスケーラブルな構造について、実際の構成例やコードを交えながら徹底解説していきます。
状態管理に求められるスケーラビリティとは
状態管理がスケーラブルであるためには、以下のような条件を満たす必要があります。
| 要件 | 説明 | 
|---|---|
| 分離性 | 状態とUI、ロジックが適切に分離されている | 
| 再利用性 | コンポーネントや状態が再利用可能な形で設計されている | 
| 拡張性 | 新しい機能を追加しても構造が崩れにくい | 
| テスト容易性 | 単体テストが書きやすく保守性が高い | 
| 型安全性 | TypeScriptなどを用いて安全な状態管理ができる | 
これらを満たすことで、数人のプロジェクトから数十人規模までスムーズにスケール可能になります。
小規模構成での状態管理の限界
まずは小さな構成から出発し、どのようにスケーラブルな構成へと変化していくのかを見ていきます。
useStateとuseContextだけの構成
tsx// App.tsx
import React, { createContext, useContext, useState } from 'react';
const UserContext = createContext(null);
export default function App() {
  const [user, setUser] = useState({ name: 'Taro', isLoggedIn: true });
  return (
    <UserContext.Provider value={{ user, setUser }}>
      <Header />
      <Dashboard />
    </UserContext.Provider>
  );
}
function Header() {
  const { user } = useContext(UserContext);
  return <div>Hello, {user.name}</div>;
}
この構成は単純ですが、以下の問題があります。
- コンテキストが1ファイルに集中してしまい、責務が曖昧になる
 - 状態が増えるとProviderツリーが深くなり、管理しにくくなる
 - コンポーネントの再利用性が落ちる(特定のコンテキストに依存)
 
カスタムフックとドメインごとの分離
ここから改善を加えていきましょう。
状態を機能ごとに分離する
tsx// features/user/hooks/useUser.ts
import { useState } from 'react';
export const useUser = () => {
  const [user, setUser] = useState({ name: 'Taro', isLoggedIn: false });
  const login = (name: string) => setUser({ name, isLoggedIn: true });
  const logout = () => setUser({ name: '', isLoggedIn: false });
  return { user, login, logout };
};
呼び出し側で使う
tsx// App.tsx
import { useUser } from './features/user/hooks/useUser';
export default function App() {
  const { user, login, logout } = useUser();
  return (
    <div>
      <h1>Hello, {user.name}</h1>
      <button onClick={() => login('Jiro')}>Login</button>
      <button onClick={logout}>Logout</button>
    </div>
  );
}
この構成では、状態のロジックをUIから完全に分離できます。
グローバルステート管理への移行:Zustandの例
中〜大規模アプリではグローバルな状態管理が必要になります。
Zustandは軽量かつシンプルでありながら、拡張性が高いためスケーラブルな構成にも適しています。
Zustandでの構成例
tsx// stores/userStore.ts
import { create } from 'zustand';
type User = {
  name: string;
  isLoggedIn: boolean;
};
type State = {
  user: User;
  login: (name: string) => void;
  logout: () => void;
};
export const useUserStore = create<State>((set) => ({
  user: { name: '', isLoggedIn: false },
  login: (name) => set({ user: { name, isLoggedIn: true } }),
  logout: () => set({ user: { name: '', isLoggedIn: false } }),
}));
tsx// components/UserPanel.tsx
import { useUserStore } from '../stores/userStore';
export const UserPanel = () => {
  const { user, login, logout } = useUserStore();
  return (
    <div>
      <p>Welcome, {user.name}</p>
      <button onClick={() => login('Hanako')}>Login</button>
      <button onClick={logout}>Logout</button>
    </div>
  );
};
Zustandを用いることで、依存性の注入を減らし、各コンポーネントが自立的に状態にアクセス可能になります。
スケーラブルな構成のパターン例
以下は状態管理を含む構成ディレクトリの一例です。
csssrc/
├── app/
│   └── providers.tsx
├── features/
│   ├── user/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── stores/
│   │   └── types/
│   ├── post/
│   └── ...
├── shared/
│   ├── components/
│   ├── hooks/
│   └── utils/
特徴としては以下の通りです。
| 特徴 | 説明 | 
|---|---|
| ドメイン単位で機能を分離 | features/user, features/post などに細分化 | 
| 状態とロジックを切り出す | stores, hooks を通じてドメインごとに責務を明確化 | 
| 共通要素はsharedへ集約 | ボタンやフォームなど共通UIは shared/components に配置 | 
状態管理のテスト戦略
スケーラブルな構成では、ユニットテストのしやすさも重要です。
Zustandのstoreをテストする例
tsimport { useUserStore } from './userStore';
describe('User Store', () => {
  it('login updates state', () => {
    const store = useUserStore.getState();
    store.login('Taro');
    expect(store.user.name).toBe('Taro');
    expect(store.user.isLoggedIn).toBe(true);
  });
  it('logout resets state', () => {
    const store = useUserStore.getState();
    store.logout();
    expect(store.user.name).toBe('');
    expect(store.user.isLoggedIn).toBe(false);
  });
});
Zustandでは getState() を用いて直接状態を取得・操作できるため、テストコードもシンプルに書けます。
他の選択肢との比較(Recoil, Jotai, Redux)
| ライブラリ | 特徴 | 適したケース | 
|---|---|---|
| Zustand | 軽量・非依存・簡潔 | 機能分離された中〜大規模構成 | 
| Recoil | アトミックな設計が可能 | 複雑な依存関係を持つ構成 | 
| Jotai | React標準に近く学習コストが低い | 小〜中規模アプリ | 
| Redux Toolkit | 明示的で堅牢、エコシステムが豊富 | チーム開発・大規模 | 
まとめ:スケーラブルなReact状態管理の最適解
Reactアプリケーションの成長に対応するには、状態管理の構成も進化させる必要があります。
本記事で紹介したように、次のステップを踏むことが重要です。
useStateからuseContext、カスタムフックへ- 状態をドメイン単位で分離し再利用性を高める
 ZustandやRecoilなどの軽量な状態管理ライブラリでグローバルステートを導入- テストや型安全性を担保し、チームでも扱いやすくする
 - フォルダ構成・責務分離により拡張性を確保する
 
こうした構成を取ることで、機能追加やリファクタリングがスムーズになり、チーム開発も格段に進めやすくなります。
ぜひ、ご自身のプロジェクトに合ったスケーラブルな状態管理構成を取り入れてみてください。
articleReact フック完全チートシート:useState から useTransition まで用途別早見表
articleReact 開発環境の作り方:Vite + TypeScript + ESLint + Prettier 完全セットアップ
articleReact とは? 2025 年版の特徴・強み・実務活用を一気に理解する完全解説
articleESLint を Yarn + TypeScript + React でゼロから構築:Flat Config 完全手順(macOS)
article【徹底比較】Preact vs React 2025:バンドル・FPS・メモリ・DX を総合評価
article既存 React プロジェクトを Preact に移行する完全ロードマップ
articleWebSocket が「200 OK で Upgrade されない」原因と対処:プロキシ・ヘッダー・TLS の落とし穴
articleWebRTC 本番運用の SLO 設計:接続成功率・初画出し時間・通話継続率の基準値
articleAstro のレンダリング戦略を一望:MPA× 部分ハイドレーションの強みを図解解説
articleWebLLM が読み込めない時の原因と解決策:CORS・MIME・パス問題を総点検
articleVitest ESM/CJS 混在で `Cannot use import statement outside a module` が出る技術対処集
articleテスト環境比較:Vitest vs Jest vs Playwright CT ― Vite プロジェクトの最適解
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来