React 状態管理ライブラリ集

React の状態管理ライブラリにはいくつかの選択肢があり、それぞれに独自の特徴と利点があります。以下に代表的なものを列挙し、その特徴を簡潔に説明します。

1. React Context API

  • 特徴: React の標準 API であり、追加のライブラリを必要としない。コンポーネントツリーを通じてデータを効率的に渡すことができる。
  • 利用シナリオ: 小規模から中規模のアプリケーション、またはグローバルな状態を管理する必要がある場合

2. Redux

  • 特徴: 予測可能な状態のコンテナを提供し、アプリケーションの状態を一箇所で管理する。アクションとリデューサーを使用して状態の更新を制御する。
  • 利用シナリオ: 大規模なアプリケーション、または複雑な状態の管理が必要な場合

3. MobX

  • 特徴: 双方向のデータバインディングを提供し、状態の変更が自動的にコンポーネントに反映される。デコレーターを使用して状態を簡単に定義できる。
  • 利用シナリオ: 高い生産性と簡潔なコードを求める場合

4. Recoil

  • 特徴: React のために設計された状態管理ライブラリで、Context API に似た概念を持ちながらも、より高度な機能を提供する。
  • 利用シナリオ: コンポーネント間で状態を共有したいが、Redux のような大規模なライブラリは必要ない場合

5. Zustand

  • 特徴: 小さくてシンプルな状態管理ライブラリ。設定が少なく、使いやすい。React 以外でも使用可能。
  • 利用シナリオ: シンプルで柔軟な状態管理を求める小規模から中規模のプロジェクト

6. Jotai

  • 特徴: 原子の概念を使用した非常に軽量な状態管理ライブラリ。コンポーネントの再レンダリングを最小限に抑える。
  • 利用シナリオ: パフォーマンスを重視し、細かい状態の管理をしたい場合

7. Redux Toolkit

  • 特徴: Redux の公式推奨ツールキットで、設定を簡単にし、開発を迅速化する。Redux のボイラープレートを減らす。
  • 利用シナリオ: Redux を使用しているが、より簡単に設定したい場合

使用例集

1. React Context API

導入方法

Context API は React に組み込まれているため、追加のインストールは必要ありません。

使用例

import React, { createContext, useContext, useState } from 'react';

// Contextの作成
const UserContext = createContext();

// Context Provider コンポーネント
const UserProvider = ({ children }) => {
  const [user, setUser] = useState({ name: 'John Doe', age: 30 });
  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
};

// コンポーネント内での使用
const UserProfile = () => {
  const { user } = useContext(UserContext);
  return (
    <div>
      <p>Name: {user.name}</p>
      <p>Age: {user.age}</p>
    </div>
  );
};

// アプリケーションのルートでProviderを使用
const App = () => (
  <UserProvider>
    <UserProfile />
  </UserProvider>
);

2. Redux

導入方法

npm install redux react-redux

使用例

import React from 'react';
import { createStore } from 'redux';
import { Provider, useSelector, useDispatch } from 'react-redux';

// アクションタイプ
const SET_USER = 'SET_USER';

// アクションクリエーター
const setUser = (user) => ({
  type: SET_USER,
  payload: user,
});

// 初期状態
const initialState = {
  user: { name: '', age: 0 },
};

// リデューサー
const rootReducer = (state = initialState, action) => {
  switch (action.type) {
    case SET_USER:
      return { ...state, user: action.payload };
    default:
      return state;
  }
};

// ストアの作成
const store = createStore(rootReducer);

// コンポーネント
const UserProfile = () => {
  const user = useSelector((state) => state.user);
  const dispatch = useDispatch();

  return (
    <div>
      <p>Name: {user.name}</p>
      <p>Age: {user.age}</p>
      <button onClick={() => dispatch(setUser({ name: 'Jane Doe', age: 28 }))}>
        Update User
      </button>
    </div>
  );
};

// アプリケーション
const App = () => (
  <Provider store={store}>
    <UserProfile />
  </Provider>
);

3. MobX

導入方法

npm install mobx mobx-react

使用例

import React from 'react';
import { makeAutoObservable } from 'mobx';
import { observer } from 'mobx-react';

// ストア
class UserStore {
  user = { name: 'John Doe', age: 30 };

  constructor() {
    makeAutoObservable(this);
  }

  setUser(user) {
    this.user = user;
  }
}

const userStore = new UserStore();

// コンポーネント
const UserProfile = observer(() => (
  <div>
    <p>Name: {userStore.user.name}</p>
    <p>Age: {userStore.user.age}</p>
    <button onClick={() => userStore.setUser({ name: 'Jane Doe', age: 28 })}>
      Update User
    </button>
  </div>
));

// アプリケーション
const App = () => <UserProfile />;

4. Recoil

導入方法

npm install recoil

使用例

import React from 'react';
import { atom, useRecoilState } from 'recoil';
import { RecoilRoot } from 'recoil';

// 状態
const userState = atom({
  key: 'userState', // unique ID (with respect to other atoms/selectors)
  default: { name: 'John Doe', age: 30 }, // default value (aka initial value)
});

// コンポーネント
const UserProfile = () => {
  const [user, setUser] = useRecoilState(userState);

  return (
    <div>
      <p>Name: {user.name}</p>
      <p>Age: {user.age}</p>
      <button onClick={() => setUser({ name: 'Jane Doe', age: 28 })}>
        Update User
      </button>
    </div>
  );
};

// アプリケーション
const App = () => (
  <RecoilRoot>
    <UserProfile />
  </RecoilRoot>
);

以下に、Zustand と Jotai の導入方法と、それを用いたシンプルなユーザー情報(例えば、ユーザー名と年齢)の管理方法を示します。

5. Zustand

導入方法

Zustand をプロジェクトに追加するには、npm または yarn を使用してライブラリをインストールします。

npm install zustand
# または
yarn add zustand

使用例

import create from 'zustand';

// ストアの定義
const useStore = create((set) => ({
  user: { name: 'John Doe', age: 30 },
  setUser: (user) => set({ user }),
}));

// コンポーネント
const UserProfile = () => {
  const { user, setUser } = useStore();
  return (
    <div>
      <p>Name: {user.name}</p>
      <p>Age: {user.age}</p>
      <button onClick={() => setUser({ name: 'Jane Doe', age: 28 })}>
        Update User
      </button>
    </div>
  );
};

export default UserProfile;

6. Jotai

導入方法

Jotai をプロジェクトに追加するために、npm または yarn を介してライブラリをインストールします。

npm install jotai
# または
yarn add jotai

使用例

import { atom, useAtom } from 'jotai';
import React from 'react';

// アトムの定義
const userAtom = atom({ name: 'John Doe', age: 30 });

// コンポーネント
const UserProfile = () => {
  const [user, setUser] = useAtom(userAtom);
  return (
    <div>
      <p>Name: {user.name}</p>
      <p>Age: {user.age}</p>
      <button onClick={() => setUser({ name: 'Jane Doe', age: 28 })}>
        Update User
      </button>
    </div>
  );
};

export default UserProfile;

比較

React の状態管理ライブラリが小規模から大規模のプロジェクトに向けてどのように分類されるか、およびそれぞれのライブラリが特定の規模のプロジェクトに適した理由を説明します。他のライブラリが規模によって推奨される理由は主に以下の点に関連しています。

小規模から中規模のプロジェクト

React Context API

  • 理由: Context API は React に組み込まれており、追加の依存関係なしで使用できます。グローバルな状態や深くネストされたコンポーネント間でのデータの受け渡しに便利ですが、状態のロジックが複雑になると管理が困難になります。
  • 不都合の例: ユーザーのアクションに応じて多数の状態を更新する必要がある大規模アプリケーションでは、Context を使用すると更新ロジックが散らばり、状態管理の難易度が高まります。これは、Context が主にデータの"配信"に焦点を当てているためです。

Zustand

  • 理由: シンプルで設定が少なく、使いやすいため、開発の迅速化に貢献します。しかし、アプリケーションの規模が大きくなるにつれ、状態の依存関係や更新の管理が複雑になります。
  • 不都合の例: 複数の状態が相互に依存する大規模なアプリケーションで Zustand を使用すると、状態間の依存関係を追跡し、予期しない副作用を避けることが難しくなる可能性があります。

大規模プロジェクト

Redux

  • 理由: アクション、ストア、リデューサーを通じてアプリケーションの状態を一箇所で管理します。この厳密なデータフローと状態の変更の予測可能性は、大規模なアプリケーションの複雑な状態管理に適しています。
  • 不都合の例: 小規模プロジェクトで Redux を使用すると、ボイラープレートの多さや設定の複雑さが開発速度を低下させる可能性があります。また、シンプルな状態管理のために大規模なライブラリを導入することはオーバーキルと見なされることがあります。

MobX

  • 理由: MobX は反応的な状態管理を提供し、状態の変更が自動的に関連するコンポーネントに反映されます。このアプローチは、大規模なアプリケーションでの細かい状態の変更を効率的に扱うことができますが、その自由度と柔軟性は、状態管理の戦略をしっかりと計画しないと、予期しない挙動やパフォーマンスの問題を引き起こす可能性があります。
  • 不都合の例: 状態管理の戦略が不十分な場合、MobX を使用すると、アプリケーションのパフォーマンスに影響を与える過度な再計算や再レンダリングが発生する可能性があります。

小~大規模プロジェクトどれでも

Jotai

  • 理由: Jotai は、非常にシンプルで軽量な状態管理ライブラリです。アトム(Atom)という概念を基にしており、これは状態の最小単位です。アトムはグローバルでもローカルでも使用でき、必要に応じてコンポーネント間で共有することができます。Recoil と比較してもっと直感的かつ柔軟性が高いとされることがあります。大規模な状態ツリーを扱う場合でも、関連するコンポーネントのみが再レンダリングされるため、パフォーマンスが向上します。
  • Jotai はそのシンプルさから、小規模プロジェクトでの使用に適していますが、その設計の柔軟性と効率的な状態更新の仕組みにより、大規模プロジェクトにも適用可能です。状態の分割と再利用が容易なため、アプリケーションの成長に伴う状態管理の複雑性をうまく扱えます。

Recoil

  • 理由: Recoil もアトミックな状態管理を採用していますが、Jotai よりも複雑な機能を提供しているため、より高度な状態管理が可能です。セレクタ(Selector)という概念を導入しており、これにより派生状態(他の状態に基づいて計算される状態)の管理が容易になります。Recoil は、コンポーネントの再レンダリングを最小限に抑えることにより、大規模なアプリケーションでも高いパフォーマンスを維持します。React の Context API と類似した構造を持ちつつ、より洗練された状態共有と更新メカニズムを提供します。
  • Recoil の提供する高度な機能とパフォーマンスの最適化は、特に大規模プロジェクトにおいてその価値を発揮します。複雑な状態依存関係や派生状態の管理が必要な場合に特に有効です。小規模プロジェクトにおいても、そのシンプルな API と React に近い設計思想により、容易に導入し使用することができます。

一般的なポイント

  • 小規模〜中規模では、設定の簡単さや開発速度が優先される傾向があります。この規模のプロジェクトでは、状態管理の複雑さが限定的であるため、シンプルなライブラリやフレームワークが適しています。
  • 大規模プロジェクトでは、状態管理の複雑さやアプリケーションのスケーラビリティが重要な考慮事項となります。厳密なデータフローや状態変更の追跡、ミドルウェアのサポートなど、より高度な機能を提供するライブラリが適しています。