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 で管理画面を最短構築:テーブル・フィルタ・権限制御の実例
articleReact でデータ取得を最適化:TanStack Query 基礎からキャッシュ戦略まで実装
articleReact クリーンアーキテクチャ実践:UI・アプリ・ドメイン・データの責務分離
articleReact フック完全チートシート:useState から useTransition まで用途別早見表
articleReact 開発環境の作り方:Vite + TypeScript + ESLint + Prettier 完全セットアップ
articleReact とは? 2025 年版の特徴・強み・実務活用を一気に理解する完全解説
articleZod 合成パターン早見表:`object/array/tuple/record/map/set/intersection` 実例集
articleバックアップ戦略の決定版:WordPress の世代管理/災害復旧の型
articleYarn 運用ベストプラクティス:lockfile 厳格化・frozen-lockfile・Bot 更新方針
articleWebSocket のペイロード比較:JSON・MessagePack・Protobuf の速度とコスト検証
articleWeb Components イベント設計チート:`CustomEvent`/`composed`/`bubbles` 実例集
articleWebRTC SDP 用語チートシート:m=・a=・bundle・rtcp-mux を 10 分で総復習
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 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来