T-CREATOR

React開発における状態管理のスケーラブルな構造について考えてみる

React開発における状態管理のスケーラブルな構造について考えてみる

React開発における状態管理のスケーラブルな構造とは 「拡張性があり、保守しやすく、チームやプロダクトの成長に対応できる構成」のとして考えます。

小さなアプリケーションでは useStateuseContext で事足りることもありますが、機能が増え、開発メンバーが増えてくると、それでは破綻してしまいます。

本記事では、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アトミックな設計が可能複雑な依存関係を持つ構成
JotaiReact標準に近く学習コストが低い小〜中規模アプリ
Redux Toolkit明示的で堅牢、エコシステムが豊富チーム開発・大規模

まとめ:スケーラブルなReact状態管理の最適解

Reactアプリケーションの成長に対応するには、状態管理の構成も進化させる必要があります。

本記事で紹介したように、次のステップを踏むことが重要です。

  1. useState から useContext、カスタムフックへ
  2. 状態をドメイン単位で分離し再利用性を高める
  3. ZustandRecoil などの軽量な状態管理ライブラリでグローバルステートを導入
  4. テストや型安全性を担保し、チームでも扱いやすくする
  5. フォルダ構成・責務分離により拡張性を確保する

こうした構成を取ることで、機能追加やリファクタリングがスムーズになり、チーム開発も格段に進めやすくなります。

ぜひ、ご自身のプロジェクトに合ったスケーラブルな状態管理構成を取り入れてみてください。

記事Article

もっと見る