redux vs react-query vs effector vs mobx vs recoil vs redux-saga vs redux-thunk vs xstate vs zustand
Reactアプリケーションにおける状態管理と非同期処理のためのライブラリ比較
reduxreact-queryeffectormobxrecoilredux-sagaredux-thunkxstatezustand類似パッケージ:

Reactアプリケーションにおける状態管理と非同期処理のためのライブラリ比較

effector、mobx、react-query、recoil、redux、redux-saga、redux-thunk、xstate、zustand は、すべてReactベースのフロントエンドアプリケーションで状態(state)や非同期処理を扱うためのライブラリです。これらのツールは、UIの状態、サーバーとのデータ同期、複雑なワークフローのモデリングなど、さまざまな課題に対応しています。たとえば redux は予測可能な単一ストアのアーキテクチャを提供し、react-query はサーバー状態の取得・キャッシュに特化しています。一方、xstate は状態遷移を明示的にモデリングする有限状態機械(FSM)を実装し、zustand や recoil はより軽量で直感的なAPIを提供します。この比較では、各ライブラリの設計思想、使いどころ、実装例を通じて、適切な選択を支援します。

npmのダウンロードトレンド

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
redux42,348,80861,509290 kB493年前MIT
react-query1,730,00450,2112.26 MB1254年前MIT
effector144,2774,8491.59 MB1591年前MIT
mobx028,2064.8 MB577日前MIT
recoil019,4312.21 MB3203年前MIT
redux-saga0-36.8 kB-1ヶ月前MIT
redux-thunk017,67826.8 kB23年前MIT
xstate030,0612.29 MB1221日前MIT
zustand058,61295.2 kB514日前MIT

Reactアプリケーションにおける状態管理と非同期処理のライブラリ比較

現代のReactアプリケーションでは、ローカルUI状態、サーバー状態、複雑な非同期ワークフローをどう扱うかが重要な設計課題です。ここでは、代表的な9つのライブラリ(effector、mobx、react-query、recoil、redux、redux-saga、redux-thunk、xstate、zustand)を、実際のコード例とともに比較し、それぞれの強みと適用場面を明らかにします。

🧠 状態管理の基本モデル:ミュータブル vs イミュータブル vs リアクティブ

まず、各ライブラリが採用する状態管理の基本方針を見てみましょう。

イミュータブルモデル(Redux系)

redux、redux-thunk、redux-saga、zustand、effector は、状態を直接変更せず、新しい状態オブジェクトを生成するイミュータブルなアプローチを採用しています。

// redux (with Redux Toolkit)
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value += 1; } // Immerにより安全にミュート可能
  }
});
// zustand
import { create } from 'zustand';
const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 }))
}));
// effector
import { createStore, createEvent } from 'effector';
const increment = createEvent();
const $counter = createStore(0).on(increment, (state) => state + 1);

ミュータブルモデル(MobX)

mobx は、状態を直接変更(ミュート)しても、自動的に依存コンポーネントを再レンダリングします。

// mobx
import { makeAutoObservable } from 'mobx';
class Counter {
  value = 0;
  constructor() { makeAutoObservable(this); }
  increment() { this.value += 1; }
}
const counter = new Counter();

細粒度リアクティブモデル(Recoil)

recoil は、Atom(状態の最小単位)とSelector(派生状態)を組み合わせて、必要な部分だけを再計算・再レンダリングします。

// recoil
import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';
const countAtom = atom({ key: 'count', default: 0 });
const doubledCount = selector({
  key: 'doubledCount',
  get: ({ get }) => get(countAtom) * 2
});

状態機械モデル(XState)

xstate は、状態遷移を明示的に定義し、不正な状態遷移を防ぎます。

// xstate
import { createMachine } from 'xstate';
const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: { on: { TOGGLE: 'active' } },
    active: { on: { TOGGLE: 'inactive' } }
  }
});

サーバー状態専用(React Query)

react-query は、UIローカル状態ではなく、サーバーから取得したデータのライフサイクルを管理します。

// react-query
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
  const { data, isLoading } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId)
  });
  // ...
}

⚙️ 非同期処理の扱い方

次に、各ライブラリが非同期処理(例:API呼び出し)をどう扱うかを比較します。

軽量な非同期処理(Redux Thunk)

redux-thunk は、アクションクリエーター内で非同期処理を直接記述できます。

// redux-thunk
const fetchUser = (userId) => async (dispatch) => {
  dispatch({ type: 'FETCH_USER_START' });
  try {
    const user = await api.getUser(userId);
    dispatch({ type: 'FETCH_USER_SUCCESS', payload: user });
  } catch (error) {
    dispatch({ type: 'FETCH_USER_FAILURE', error });
  }
};

複雑な非同期フロー(Redux Saga)

redux-saga は、ジェネレーター関数で非同期処理を宣言的に記述し、キャンセルや並列処理を容易にします。

// redux-saga
import { call, put, takeEvery, cancel } from 'redux-saga/effects';
function* fetchUserSaga(action) {
  try {
    const user = yield call(api.getUser, action.payload.userId);
    yield put({ type: 'FETCH_USER_SUCCESS', user });
  } catch (error) {
    yield put({ type: 'FETCH_USER_FAILURE', error });
  }
}
function* watchFetchUser() {
  yield takeEvery('FETCH_USER_REQUEST', fetchUserSaga);
}

ビジネスロジックの分離(Effector)

effector は、イベント、エフェクト、ストアを分離し、非同期処理を宣言的に接続します。

// effector
import { createEvent, createEffect, createStore } from 'effector';
const fetchUserFx = createEffect(async (userId) => {
  return await api.getUser(userId);
});
const $user = createStore(null).on(fetchUserFx.doneData, (_, user) => user);
const fetchUser = createEvent();
fetchUserFx.watch((userId) => console.log('Fetching user:', userId));

MobXでの非同期処理

mobx では、アクション内で非同期処理を直接記述できますが、flow ユーティリティを使うとジェネレーター風に書けます。

// mobx
import { flow } from 'mobx';
class UserStore {
  user = null;
  fetchUser = flow(function* (this: UserStore, userId) {
    try {
      this.user = yield api.getUser(userId);
    } catch (error) {
      console.error(error);
    }
  });
}

Zustandでの非同期処理

zustand は、ストアのセッター内で非同期処理を直接記述できます。

// zustand
const useStore = create((set) => ({
  user: null,
  fetchUser: async (userId) => {
    try {
      const user = await api.getUser(userId);
      set({ user });
    } catch (error) {
      console.error(error);
    }
  }
}));

React Queryでの非同期処理

react-query は、queryFn に非同期処理を渡すだけで、自動的にローディング・エラー・再試行・キャッシュを処理します。

// react-query
const { data, error, isLoading } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => api.getUser(userId),
  retry: 3
});

🔁 派生状態と計算の効率化

UIでは、元の状態から計算された値(例:フィルタ済みリスト、合計金額)をよく使います。各ライブラリはこれをどう扱うでしょうか?

Redux ToolkitのcreateSelector

// redux
import { createSelector } from '@reduxjs/toolkit';
const selectItems = (state) => state.items;
const selectFilter = (state) => state.filter;
const selectFilteredItems = createSelector(
  [selectItems, selectFilter],
  (items, filter) => items.filter(item => item.category === filter)
);

RecoilのSelector

// recoil
const filteredItems = selector({
  key: 'filteredItems',
  get: ({ get }) => {
    const items = get(itemsAtom);
    const filter = get(filterAtom);
    return items.filter(item => item.category === filter);
  }
});

MobXのcomputed

// mobx
class Store {
  items = [];
  filter = '';
  constructor() { makeAutoObservable(this); }
  get filteredItems() {
    return this.items.filter(item => item.category === this.filter);
  }
}

Zustandの派生状態

zustand は、subscribe や useMemo で派生状態を実現しますが、公式推奨は zustand/middleware の combine や vanilla ストアの利用です。

// zustand
const useStore = create((set, get) => ({
  items: [],
  filter: '',
  getFilteredItems: () => {
    const { items, filter } = get();
    return items.filter(item => item.category === filter);
  }
}));

Effectorの$store.map

// effector
const $items = createStore([]);
const $filter = createStore('');
const $filteredItems = $items.combine($filter, (items, filter) =>
  items.filter(item => item.category === filter)
);

🧩 実用的な選択ガイド

サーバー状態 vs ローカル状態

  • サーバー状態(APIデータ): react-query を使うべきです。キャッシュ、再取得、エラーハンドリングが自動で、他のローカル状態管理と併用可能です。
  • ローカルUI状態: 上記の他のライブラリ(zustand、redux、mobx など)を検討します。

プロジェクト規模とチームスキル

  • 小規模/個人プロジェクト: zustand か mobx。セットアップが簡単で、開発速度が速い。
  • 中〜大規模/チーム開発: redux(Redux Toolkit)か effector。予測可能な状態管理と強力なデバッグ機能がチーム開発を助けます。
  • 複雑なワークフロー(例:決済フロー): xstate で状態遷移を明示的にモデリングすると、バグが減ります。

パフォーマンス要件

  • 細粒度更新が必要(例:巨大なリストで一部だけ更新): recoil か mobx。不要な再レンダリングを避けられます。
  • シンプルなアプリ: zustand で十分。Context APIの欠点を回避しつつ、オーバーヘッドが少ないです。

⚠️ 注意点と非推奨情報

  • recoil: Meta社が2023年に開発を一時停止しており、将来的なサポートに不透明性があります。新規プロジェクトでは慎重に検討すべきです。
  • redux-thunk vs redux-saga: 単純な非同期処理なら redux-thunk で十分ですが、キャンセルや並列処理が必要なら redux-saga を選びましょう。
  • mobx: ミュータブル操作は便利ですが、過度に使うと状態の追跡が難しくなるため、チーム内でのコーディング規約が重要です。

📊 まとめ:各ライブラリの特徴と用途

ライブラリ主な用途学習コスト推奨シナリオ
redux予測可能なグローバル状態管理中大規模アプリ、厳格なテスト要件
redux-thunkReduxの軽量非同期処理低単純なAPI呼び出し
redux-saga複雑な非同期ワークフロー高キャンセル可能な処理、並列処理
zustand軽量グローバル状態管理低小〜中規模アプリ、素早い開発
mobxミュータブルなリアクティブ状態中オブジェクト指向的な状態管理を好むチーム
recoil細粒度な状態と派生状態中複雑な依存関係を持つUI(注意:開発停止中)
effectorイベント駆動のビジネスロジック高大規模アプリ、副作用の分離が必要な場合
xstate状態遷移の明示的モデリング中フォーム、モーダル、ゲームなど状態が明確なUI
react-queryサーバー状態の管理低あらゆるAPIデータの取得・キャッシュ(他のローカル状態管理と併用)

💡 最終的なアドバイス

  • まず react-query を導入する: サーバー状態の管理はほぼ必須です。
  • ローカル状態は zustand から始める: 小さく始め、必要に応じて redux や effector に移行できます。
  • 複雑なUIワークフローには xstate を検討: 状態遷移図を描けるなら、コードでも表現すべきです。
  • recoil は現状避ける: 開発停止中のため、新規プロジェクトではリスクが高いです。

状態管理ライブラリは「銀の弾丸」ではありません。プロジェクトの要件、チームのスキル、将来の保守性を総合的に判断し、適切なツールを選びましょう。

選び方: redux vs react-query vs effector vs mobx vs recoil vs redux-saga vs redux-thunk vs xstate vs zustand

  • redux:

    reduxは、状態の変更を完全に予測可能にし、時間旅行デバッグや厳格なテストを必要とする大規模アプリケーションに適しています。ただし、ボイラープレートが多く、小規模プロジェクトでは開発速度を阻害する可能性があります。現在はRedux Toolkitにより定型コードは大幅に削減されていますが、それでも他の軽量ライブラリと比べると重めです。

  • react-query:

    react-queryは、サーバーから取得したデータのキャッシング、再取得、エラーハンドリングなど、サーバー状態の管理に特化しています。UIローカル状態ではなく、API経由で取得するデータを扱うプロジェクトでは、ほぼ必須の選択肢です。ローカル状態管理とは併用が前提であり、単体でアプリ全体の状態を管理する用途には向きません。

  • effector:

    effectorは、イベント駆動で副作用を分離できる強力なリアクティブシステムを必要とするプロジェクトに適しています。Reduxのような予測可能な状態管理を維持しつつ、非同期処理やビジネスロジックを宣言的に記述したい場合に有効です。ただし、学習曲線がやや急で、小規模アプリにはオーバースペックになる可能性があります。TypeScriptとの親和性も高く、大規模チームでの保守性を重視する場合に選ぶと良いでしょう。

  • mobx:

    mobxは、オブジェクト指向的に状態を直接変更しながらも、自動的に再レンダリングをトリガーしたい場合に最適です。リアクティブなプログラミングモデルを好む開発者や、状態の更新ロジックをシンプルに保ちたいチームに向いています。ただし、過度なミュータブル操作はデバッグを難しくする可能性があるため、コードレビューと規約によるガードが必要です。

  • recoil:

    recoilは、AtomとSelectorという単位で細粒度な状態管理を実現し、派生状態の計算を効率的に行いたい場合に有効です。特に、複雑な依存関係を持つUI(例:フィルタ+ソート+検索)でパフォーマンスを最適化したい場合に適しています。ただし、Metaによって開発が一時停止されており、将来的なサポートに不安がある点は考慮すべきです。

  • redux-saga:

    redux-sagaは、複雑な非同期フロー(例:キャンセル可能なAPI呼び出し、並列処理、ウォッチ処理)を宣言的に記述したい場合に選ぶべきです。ジェネレーター関数を活用して副作用を分離できるため、テストしやすく、ビジネスロジックの可読性を高められます。ただし、ジェネレーターの理解が必要で、簡単な非同期処理にはオーバーエンジニアリングになりがちです。

  • redux-thunk:

    redux-thunkは、Reduxで非同期アクションを簡単に導入したい場合に最適な軽量ミドルウェアです。API呼び出しなどの基本的な非同期処理を、既存のReduxフローに最小限の変更で組み込めます。ただし、複雑なワークフロー(例:キャンセル、並列処理)には向かず、そのような要件がある場合はredux-sagaやredux-observableを検討すべきです。

  • xstate:

    xstateは、UIの状態遷移が明確に定義できる(例:フォームのステップ、モーダルの表示状態、ゲームのシーン)場合に非常に強力です。有限状態機械(FSM)や状態チャートを用いて、バグの原因になりやすい曖昧な状態を排除できます。ただし、すべての状態を事前にモデリングする必要があり、柔軟性を求めるケースでは制約が強すぎるかもしれません。

  • zustand:

    zustandは、Reduxのような集中管理の利点を残しつつ、ボイラープレートをほぼゼロに抑えたい場合に最適です。シンプルなAPIでグローバル状態を管理でき、Context APIの欠点(再レンダリングの無駄)を回避します。小〜中規模アプリや、素早くプロトタイプを作りたいチームに強くおすすめできます。

redux のREADME

Redux Logo

Redux is a predictable state container for JavaScript apps.

It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. On top of that, it provides a great developer experience, such as live code editing combined with a time traveling debugger.

You can use Redux together with React, or with any other view library. The Redux core is tiny (2kB, including dependencies), and has a rich ecosystem of addons.

Redux Toolkit is our official recommended approach for writing Redux logic. It wraps around the Redux core, and contains packages and functions that we think are essential for building a Redux app. Redux Toolkit builds in our suggested best practices, simplifies most Redux tasks, prevents common mistakes, and makes it easier to write Redux applications.

GitHub Workflow Status npm version npm downloads redux channel on discord

Installation

Create a React Redux App

The recommended way to start new apps with React and Redux Toolkit is by using our official Redux Toolkit + TS template for Vite, or by creating a new Next.js project using Next's with-redux template.

Both of these already have Redux Toolkit and React-Redux configured appropriately for that build tool, and come with a small example app that demonstrates how to use several of Redux Toolkit's features.

# Vite with our Redux+TS template
# (using the `degit` tool to clone and extract the template)
npx degit reduxjs/redux-templates/packages/vite-template-redux my-app

# Next.js using the `with-redux` template
npx create-next-app --example with-redux my-app

We do not currently have official React Native templates, but recommend these templates for standard React Native and for Expo:

npm install @reduxjs/toolkit react-redux

For the Redux core library by itself:

npm install redux

For more details, see the Installation docs page.

Documentation

The Redux core docs are located at https://redux.js.org, and include the full Redux tutorials, as well usage guides on general Redux patterns:

The Redux Toolkit docs are available at https://redux-toolkit.js.org, including API references and usage guides for all of the APIs included in Redux Toolkit.

Learn Redux

Redux Essentials Tutorial

The Redux Essentials tutorial is a "top-down" tutorial that teaches "how to use Redux the right way", using our latest recommended APIs and best practices. We recommend starting there.

Redux Fundamentals Tutorial

The Redux Fundamentals tutorial is a "bottom-up" tutorial that teaches "how Redux works" from first principles and without any abstractions, and why standard Redux usage patterns exist.

Help and Discussion

The #redux channel of the Reactiflux Discord community is our official resource for all questions related to learning and using Redux. Reactiflux is a great place to hang out, ask questions, and learn - please come and join us there!

Before Proceeding Further

Redux is a valuable tool for organizing your state, but you should also consider whether it's appropriate for your situation. Please don't use Redux just because someone said you should - instead, please take some time to understand the potential benefits and tradeoffs of using it.

Here are some suggestions on when it makes sense to use Redux:

  • You have reasonable amounts of data changing over time
  • You need a single source of truth for your state
  • You find that keeping all your state in a top-level component is no longer sufficient

Yes, these guidelines are subjective and vague, but this is for a good reason. The point at which you should integrate Redux into your application is different for every user and different for every application.

For more thoughts on how Redux is meant to be used, please see:

Basic Example

The whole global state of your app is stored in an object tree inside a single store. The only way to change the state tree is to create an action, an object describing what happened, and dispatch it to the store. To specify how state gets updated in response to an action, you write pure reducer functions that calculate a new state based on the old state and the action.

Redux Toolkit simplifies the process of writing Redux logic and setting up the store. With Redux Toolkit, the basic app logic looks like:

import { createSlice, configureStore } from '@reduxjs/toolkit'

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    incremented: state => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1
    },
    decremented: state => {
      state.value -= 1
    }
  }
})

export const { incremented, decremented } = counterSlice.actions

const store = configureStore({
  reducer: counterSlice.reducer
})

// Can still subscribe to the store
store.subscribe(() => console.log(store.getState()))

// Still pass action objects to `dispatch`, but they're created for us
store.dispatch(incremented())
// {value: 1}
store.dispatch(incremented())
// {value: 2}
store.dispatch(decremented())
// {value: 1}

Redux Toolkit allows us to write shorter logic that's easier to read, while still following the original core Redux behavior and data flow.

Logo

You can find the official logo on GitHub.

Change Log

This project adheres to Semantic Versioning. Every release, along with the migration instructions, is documented on the GitHub Releases page.

License

MIT