effector vs mobx vs redux vs rxjs
前端状態管理と非同期処理のアーキテクチャ比較
effectormobxreduxrxjs類似パッケージ:

前端状態管理と非同期処理のアーキテクチャ比較

effector、mobx、redux、rxjs は、JavaScript アプリケーションの状態管理と非同期処理を扱うための代表的なライブラリです。redux は予測可能な状態コンテナとして広く採用されており、mobx は可変状態と自動依存関係追跡を特徴とします。effector は型安全性とパフォーマンスを重視した関数型アプローチを提供し、rxjs はストリームベースのリアクティブプログラミングに特化しています。これらはそれぞれ異なる哲学を持ち、プロジェクトの規模や複雑さ、チームの好みに合わせて選択されます。

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

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
effector04,8531.59 MB1651年前MIT
mobx028,2104.84 MB455日前MIT
redux061,480290 kB153年前MIT
rxjs031,6944.5 MB1902年前Apache-2.0

前端状態管理と非同期処理のアーキテクチャ比較

effector、mobx、redux、rxjs は、JavaScript アプリケーションの状態管理と非同期処理を扱うための代表的なライブラリですが、それぞれアプローチが異なります。redux は不変性と単一方向データフローを重視し、mobx は可変状態と自動反応を特徴とします。effector はイベント駆動の型安全な状態管理を提供し、rxjs はストリームベースの処理に特化しています。実務でこれらをどう使い分けるか、具体的なコードを通じて比較します。

🗂️ 状態の定義方法:ストア vs オブザーバブル

状態をどのように宣言するかは、ライブラリの哲学を如実に表しています。

redux は状態を単一のオブジェクト(ストア)で管理し、変更はレデューサーを通じて行います。

  • 状態は不変(immutable)である必要があります。
  • @reduxjs/toolkit の createSlice を使うのが現在の標準です。
// redux: スライスによる状態定義
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value += 1; }
  }
});
export const { increment } = counterSlice.actions;

mobx はクラスやオブジェクトをそのまま状態として扱います。

  • 状態は可変(mutable)です。
  • makeAutoObservable で自動的に反応性を付与します。
// mobx: クラスによる状態定義
import { makeAutoObservable } from 'mobx';

class Counter {
  value = 0;
  constructor() {
    makeAutoObservable(this);
  }
  increment() {
    this.value += 1;
  }
}

effector はストアとイベントを分離して定義します。

  • 状態は createStore、更新は createEvent でトリガーします。
  • 型推論が強く働きます。
// effector: ストアとイベントの定義
import { createStore, createEvent } from 'effector';

const increment = createEvent();
const $counter = createStore(0).on(increment, (count) => count + 1);

rxjs は状態を「時間の経過とともに変化する値のストリーム」として扱います。

  • 状態そのものより、値の流れ(Observable)を定義します。
  • BehaviorSubject を使うと現在の値を保持できます。
// rxjs: BehaviorSubject による状態定義
import { BehaviorSubject } from 'rxjs';

const counter$ = new BehaviorSubject(0);
// 値の更新は next() で行います
counter$.next(counter$.value + 1);

🔄 状態の更新:アクション vs 直接変更 vs イベント

状態を変更する際の書き方も大きく異なります。

redux はアクションをディスパッチすることで間接的に状態を変更します。

  • 直接状態を書き換えることはできません。
  • 意図した変更のみを記録として残せます。
// redux: アクションのディスパッチ
import { useDispatch } from 'react-redux';

function Counter() {
  const dispatch = useDispatch();
  return <button onClick={() => dispatch(increment())}>+</button>;
}

mobx はメソッド内で直接状態を変更します。

  • 特別なディスパッチ処理は不要です。
  • アクション内で変更すれば自動的に通知されます。
// mobx: 直接の変更
import { observer } from 'mobx-react-lite';

function Counter({ store }) {
  return <button onClick={() => store.increment()}>+</button>;
}

effector はイベントをトリガー(呼ぶ)ことで状態を更新します。

  • 関数を呼び出す感覚に近いです。
  • 副作用と状態更新が明確に分離されます。
// effector: イベントのトリガー
import { useUnit } from 'effector-react';

function Counter() {
  const increment = useUnit(incrementEvent);
  return <button onClick={increment}>+</button>;
}

rxjs はストリームに新しい値を送り込みます。

  • Subject や BehaviorSubject の next メソッドを使います。
  • 購読者がその値を受け取って処理します。
// rxjs: ストリームへの値送信
const button = document.querySelector('button');
button.addEventListener('click', () => {
  counter$.next(counter$.value + 1);
});

⚡ 非同期処理:サンク vs 効果 vs オペレーター

API 通信などの非同期処理をどう扱うかも重要な選定基準です。

redux はミドルウェア(thunk)や createAsyncThunk を使います。

  • 開始、成功、失敗の状態を明示的に管理します。
  • 処理の流れが追いやすいです。
// redux: 非同期スライス
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

const fetchData = createAsyncThunk('data/fetch', async () => {
  const res = await fetch('/api');
  return res.json();
});

const slice = createSlice({
  name: 'data',
  initialState: { status: 'idle', data: null },
  extraReducers: (builder) => {
    builder.addCase(fetchData.fulfilled, (state, action) => {
      state.data = action.payload;
    });
  }
});

mobx は async/await をアクション内でそのまま使えます。

  • 非同期処理中も状態の変更が可能です。
  • 複雑なフローでは制御が難しくなることがあります。
// mobx: 非同期アクション
class Store {
  data = null;
  async fetchData() {
    const res = await fetch('/api');
    this.data = await res.json();
  }
}

effector は createEffect を使って副作用を管理します。

  • 成功時(done)と失敗時(fail)のイベントが自動生成されます。
  • 型安全に非同期処理を扱えます。
// effector: 効果(Effect)
import { createEffect } from 'effector';

const fetchData = createEffect(async () => {
  const res = await fetch('/api');
  return res.json();
});

// 結果をストアに繋ぐ
const $data = createStore(null).on(fetchData.doneData, (_, payload) => payload);

rxjs はオペレーター(switchMap, mergeMap など)で制御します。

  • キャンセル処理やデバウンスが非常に強力です。
  • 複雑なストリーム合成が可能ですが、学習コストが高いです。
// rxjs: オペレーターによる制御
import { fromEvent } from 'rxjs';
import { switchMap } from 'rxjs/operators';

fromEvent(button, 'click').pipe(
  switchMap(() => fetch('/api'))
).subscribe(res => {
  // 処理結果
});

🎣 React との連携:フック vs オブザーバー

React コンポーネント内でどう状態を使うかも開発体験に影響します。

redux は useSelector と useDispatch が標準です。

  • 必要な状態だけを選択して購読します。
  • 再レンダリングの制御が容易です。
// redux: フックによる連携
import { useSelector } from 'react-redux';

function Counter() {
  const count = useSelector((state) => state.counter.value);
  return <div>{count}</div>;
}

mobx は observer 高階コンポーネントまたはフックを使います。

  • 使っている状態が自動的に追跡されます。
  • 明示的な選択は不要ですが、最適化に注意が必要です。
// mobx: observer による連携
import { observer } from 'mobx-react-lite';

const Counter = observer(({ store }) => {
  return <div>{store.value}</div>;
});

effector は useUnit フックを使用します。

  • ストアとイベントをまとめてバインディングできます。
  • 不要な再レンダリングを防ぐ仕組みが内置されています。
// effector: useUnit による連携
import { useUnit } from 'effector-react';

function Counter() {
  const { count, inc } = useUnit({ count: $counter, inc: increment });
  return <div onClick={inc}>{count}</div>;
}

rxjs は専用のフック(useObservable 等)を自作またはライブラリから入れます。

  • 標準の React 機能ではないため、設定が必要です。
  • ストリームのライフサイクル管理を自分で責任を持つ必要があります。
// rxjs: カスタムフックによる連携(例)
import { useObservable } from 'react-rxjs'; // 第三者ライブラリ例

function Counter() {
  const [count] = useObservable(() => counter$, 0);
  return <div>{count}</div>;
}

🤝 共通点:状態と反応性

これら 4 つのライブラリは、異なるアプローチを取りながらも、いくつかの重要な共通点を持っています。

1. ⚛️ 反応性(Reactivity)のサポート

  • 状態の変化を検知して、UI や他のロジックに通知する仕組みを全て持っています。
  • 手動で DOM を操作する必要がなく、データ駆動で開発できます。
// 全てのライブラリに共通する概念:変化の通知
// redux: dispatch -> reducer -> store update
// mobx: action -> observable update -> reaction
// effector: event -> store update -> subscription
// rxjs: next() -> observable stream -> subscribe

2. 🛠️ 開発ツールの充実

  • それぞれデバッグ用のツールや拡張機能を提供しています。
  • 状態の変化を追跡したり、時間を巻き戻したりする機能があります。
// 例:Redux DevTools, MobX DevTools, Effector Inspector
// 各ライブラリはブラウザ拡張やロギング機能で状態変化を可視化します

3. 📦 拡張性とエコシステム

  • 大規模なコミュニティとサードパーティライブラリが存在します。
  • 永続化(persist)、ロギング、テストなどの機能は外部ライブラリで補えます。
// 例:redux-persist, mobx-persist-store, effector-storage
// 状態を localStorage などに保存する仕組みが各エコシステムに存在します

📊 比較サマリー

特徴reduxmobxeffectorrxjs
状態管理不変(Immutable)可変(Mutable)不変(Immutable)ストリーム
更新方法アクションディスパッチ直接変更イベントトリガーnext()
非同期Thunk / RTK Queryasync/awaitEffectsOperators
React 連携useSelectorobserveruseUnitCustom Hook
学習コスト中(規約が多い)低(直感的)中(概念理解)高(オペレーター)
型安全性高(RTK + TS)中非常に高い高

💡 結論と推奨

redux は、標準的で予測可能な状態管理を求める場合に最適です。特にチーム開発で規約を統一したい時や、デバッグ機能を重視する時に選ばれます。現在は Redux Toolkit を使うことで、以前より手軽に導入できます。

mobx は、少人数のチームや、オブジェクト指向的な設計を好むプロジェクトに向いています。ボイラープレートを減らして素早く開発したい場合に有効ですが、大規模化すると状態の変更箇所が追跡しにくくなるリスクがあります。

effector は、型安全性とパフォーマンスを両立したいモダンなプロジェクトに推奨されます。イベント駆動アーキテクチャを採用することで、複雑なロジックも整理しやすく、テストも容易です。

rxjs は、状態管理そのものというより、複雑な非同期フローやイベント処理を制御するためのツールとして捉えるべきです。フォームのバリデーション、リアルタイム通信、キャンセル処理など、ストリーム処理が本領を発揮する場面で選択します。

最終的なアドバイス:単純な状態管理なら redux か effector、複雑な非同期フロー制御なら rxjs、手軽さと可変状態の利便性なら mobx を検討してください。プロジェクトの要件とチームの習熟度に合わせて選定することが重要です。

選び方: effector vs mobx vs redux vs rxjs

  • effector:

    型安全性とパフォーマンスを最優先する場合に effector を選択します。ボイラープレートが少なく、イベント駆動のアーキテクチャで複雑なロジックを整理したいプロジェクトに適しています。特に TypeScript との相性が良く、大規模な状態管理が必要な場合に有効です。

  • mobx:

    オブジェクト指向的な可変状態管理を好む場合に mobx を選択します。クラスやオブジェクトを直接変更できるため、学習コストが低く、既存の OOP 知識を活かせます。複雑な依存関係を持つ状態グラフを扱う際に強力です。

  • redux:

    予測可能な状態更新と豊富なエコシステムを必要とする場合に redux(特に Redux Toolkit)を選択します。デバッグツール(Time Travel)やミドルウェアによる拡張性が求められる大規模プロジェクトや、多くのサードパーティライブラリとの連携が必要な場合に適しています。

  • rxjs:

    複雑な非同期ストリームやイベント処理を扱う場合に rxjs を選択します。状態管理そのものよりも、データフローの変換や制御、キャンセル処理などを細かく制御したい時に真価を発揮します。リアルタイムデータ処理やフォーム制御などに適しています。

effector のREADME

Effector Comet Logo


join gitter build status discord chat become a patron Ask DeepWiki

☄️ effector

Business logic with ease

Visit effector.dev for docs, guides and examples

Table of Contents

Introduction

Effector implements business logic with ease for Javascript apps (React/React Native/Vue/Svelte/Node.js/Vanilla), allows you to manage data flow in complex applications. Effector provides best TypeScript support out of the box.

Effector follows five basic principles:

  • Application stores should be as light as possible - the idea of adding a store for specific needs should not be frightening or damaging to the developer.
  • Application stores should be freely combined - data that the application needs can be statically distributed, showing how it will be converted in runtime.
  • Autonomy from controversial concepts - no decorators, no need to use classes or proxies - this is not required to control the state of the application and therefore the api library uses only functions and plain js objects
  • Predictability and clarity of API - a small number of basic principles are reused in different cases, reducing the user's workload and increasing recognition. For example, if you know how .watch works for events, you already know how .watch works for stores.
  • The application is built from simple elements - space and way to take any required business logic out of the view, maximizing the simplicity of the components.

Installation

You can use any package manager

npm add effector

React

To getting started read our article how to write React and Typescript application.

npm add effector effector-react

SolidJS

npm add effector effector-solid

Vue

npm add effector effector-vue

Svelte

Svelte works with effector out of the box, no additional packages needed. See word chain game application written with svelte and effector.

CDN

Documentation

For additional information, guides and api reference visit our documentation site

Packages

Articles

Community

Online playground

You can try effector with online playground

Code sharing, Typescript and react supported out of the box. Playground repository

DevTools

Use effector-logger for printing updates to console, displaying current store values with ui or connecting application to familiar redux devtools


More examples in documentation

Learn more

Support us

Your support allows us to improve the developer experience 🧡.

Contributors

Dmitry/
Dmitry
andretshurotshka/
andretshurotshka
Sova/
Sova
Alexander
Alexander Khoroshikh
popuguy/
popuguy
Igor
Igor Kamyşev
pxbuffer/
pxbuffer
Valeriy
Valeriy Kobzar
Yan/
Yan
Ruslan
Ruslan @doasync
Illia
Illia Osmanov
mg901/
mg901
Igor
Igor Ryzhov
Nikita
Nikita Kungurcev
Edward
Edward Gigolaev
Viktor/
Viktor
Arthur
Arthur Irgashev
Ilya/
Ilya
Ainur/
Ainur
Ilya
Ilya Olovyannikov
Mikhail
Mikhail Kireev
Arutyunyan
Arutyunyan Artem
Dmitrij
Dmitrij Shuleshov
Nikita
Nikita Nafranets
Ivan
Ivan Savichev
Aleksandr
Aleksandr Osipov
bakugod/
bakugod
Зухриддин
Зухриддин Камильжанов
Mikhail
Mikhail Krilov
Victor
Victor Didenko
Viktor
Viktor Pasynok
Kirill
Kirill Mironov
Andrei/
Andrei
Denis/
Denis
Filipkin
Filipkin Denis
Ivan/
Ivan
Ivanov
Ivanov Vadim
sergey20x25/
sergey20x25
Rastrapon/
Rastrapon
Dan/
Dan
Bohdan
Bohdan Petrov
Bartłomiej
Bartłomiej Wendt
Andrei
Andrei Antropov
☃︎/
☃︎
xaota/
xaota
cqh/
cqh
Aldiyar
Aldiyar Batyrbekov
Vladimir
Vladimir Ivakin
Vitaly
Vitaly Afonin
Victor/
Victor
Tauyekel
Tauyekel Kunzhol
Ivan/
Ivan
Sozonov/
Sozonov
Samir/
Samir
Renat
Renat Sagdeev
Kirill/
Kirill
Denis
Denis Sikuler
Chshanovskiy
Chshanovskiy Maxim
Arsen-95/
Arsen-95
Anton
Anton Yurovskykh
Anton
Anton Kosykh
Aleksandr
Aleksandr Belov
Usman
Usman Yunusov
Vasili
Vasili Sviridov
Vasili
Vasili Svirydau
Victor
Victor Kolb
Vladislav/
Vladislav
Vladislav
Vladislav Melnikov
Vladislav
Vladislav Botvin
Will
Will Heslam
xxxxue/
xxxxue
The
The Gitter Badger
Simon
Simon Muravev
Shiyan7/
Shiyan7
Sergey
Sergey Belozyorcev
Satya
Satya Rohith
Roman
Roman Paravaev
Roman/
Roman
Robert
Robert Kuzhin
Raman
Raman Aktsisiuk
Rachael
Rachael Dawn
vladthelittleone/
vladthelittleone
Vladimir/
Vladimir
roman/
roman
Eris/
Eris
lightningmq/
lightningmq
Kirill
Kirill Leushkin
kanno/
kanno
Ilya/
Ilya
ilfey/
ilfey
Houston
Houston (Bot)
Grigory
Grigory Zaripov
dmitryplyaskin/
dmitryplyaskin
Stanislav/
Stanislav
Артём
Артём Жолудь
ansunrisein/
ansunrisein
Anatoly
Anatoly Kopyl
Yesset
Yesset Zhussupov
Rasul
Rasul
bigslycat/
bigslycat
Dmitry
Dmitry Dudin
Dmitry/
Dmitry
Denis
Denis Skiba
Dinislam
Dinislam Maushov
Ayu/
Ayu
David/
David
Egor
Egor Gorochkin
Amar
Amar Sood
Александр/
Александр
Alexander/
Alexander
Alex
Alex Anokhin
jokecodes/
jokecodes
Alex
Alex Arro
Aleksandr
Aleksandr Grigorii
Abel
Abel Soares Siqueira
7iomka/
7iomka
Abdukerim
Abdukerim Radjapov
0xflotus/
0xflotus
Pavel
Pavel Hrakovich
Oleh/
Oleh
Oleg/
Oleg
Mike
Mike Cann
Nikita
Nikita Svoyachenko
Marco
Marco Pasqualetti
Ludovic
Ludovic Dem
Leniorko/
Leniorko
Lebedev
Lebedev Konstantin
Joel
Joel Bandi
Jesse
Jesse Jackson
Jan
Jan Keromnes
Ivan/
Ivan
Infant
Infant Frontender
Ilya
Ilya Martynov
Gleb
Gleb Kotovsky
Gabriel
Gabriel Husek
Ed
Ed Prince

Tested with browserstack