effector vs mobx vs react-query vs recoil vs redux vs redux-saga vs redux-thunk vs xstate vs zustand
前端状态管理与数据流方案深度对比
effectormobxreact-queryrecoilreduxredux-sagaredux-thunkxstatezustand类似的npm包:

前端状态管理与数据流方案深度对比

effectormobxreact-queryrecoilreduxredux-sagaredux-thunkxstatezustand 都是用于管理前端应用状态或处理异步数据流的 JavaScript 库,但它们在设计哲学、适用场景和 API 风格上存在显著差异。redux 及其中间件(如 redux-thunkredux-saga)提供了一套可预测的状态容器,适合大型复杂应用;mobxeffector 基于响应式编程模型,强调自动追踪依赖和细粒度更新;recoilzustand 是轻量级 React 状态库,分别采用原子化和 hooks 驱动的方式;react-query 专注于服务端状态管理,如缓存、同步和后台更新;而 xstate 则基于状态机理论,适用于具有明确状态转换逻辑的交互场景。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
effector59,0544,8221.59 MB1526 个月前MIT
mobx028,1834.35 MB806 个月前MIT
react-query048,8662.26 MB1503 年前MIT
recoil019,5202.21 MB3223 年前MIT
redux061,459290 kB412 年前MIT
redux-saga022,4776.25 kB455 个月前MIT
redux-thunk017,72626.8 kB12 年前MIT
xstate029,3422.25 MB1511 个月前MIT
zustand057,47495 kB44 天前MIT

前端状态管理与数据流方案深度对比

在现代前端开发中,状态管理早已超越简单的 useState。面对复杂交互、服务端数据同步、跨组件通信等挑战,开发者需要在多种范式中做出权衡:是选择可预测的单向数据流,还是响应式的自动追踪?是聚焦客户端状态,还是统一服务端缓存?本文将从真实工程视角,深入比较九种主流方案 —— effectormobxreact-queryrecoilreduxredux-sagaredux-thunkxstatezustand

🧠 核心理念与适用边界

客户端状态 vs 服务端状态

首先区分两类状态:

  • 客户端状态:UI 开关、表单输入、本地计算结果等,生命周期在浏览器内。
  • 服务端状态:从 API 获取的用户资料、商品列表等,源头在后端。

react-query 专为服务端状态设计,其他库主要处理客户端状态。混用两者(如用 Redux 存 API 数据)常导致冗余和不一致。

编程范式差异

  • 命令式/可变mobx 允许直接修改状态,依赖自动追踪。
  • 声明式/不可变reduxzustandrecoil 要求通过 action 或 setter 更新状态。
  • 状态机xstate 将状态视为有限状态机,转换由事件驱动。
  • 响应式流effector 将状态、事件、效应视为独立单元,通过声明式图连接。

🛠️ 基础状态管理:定义、读取与更新

我们以一个简单计数器为例,展示各库的基本用法。

redux

// store.js
import { createStore } from 'redux';

const initialState = { count: 0 };
const reducer = (state = initialState, action) => {
  if (action.type === 'increment') return { count: state.count + 1 };
  return state;
};

const store = createStore(reducer);

// 在组件中
import { useSelector, useDispatch } from 'react-redux';

function Counter() {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();
  return <button onClick={() => dispatch({ type: 'increment' })}>{count}</button>;
}

zustand

// store.js
import { create } from 'zustand';

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 }))
}));

// 在组件中
function Counter() {
  const { count, increment } = useStore();
  return <button onClick={increment}>{count}</button>;
}

recoil

// atoms.js
import { atom, useRecoilState } from 'recoil';

const countAtom = atom({
  key: 'count',
  default: 0
});

// 在组件中
function Counter() {
  const [count, setCount] = useRecoilState(countAtom);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

mobx

// store.js
import { makeAutoObservable } from 'mobx';
import { observer } from 'mobx-react-lite';

class CounterStore {
  count = 0;
  constructor() { makeAutoObservable(this); }
  increment() { this.count++; }
}
const counterStore = new CounterStore();

// 在组件中
const Counter = observer(() => (
  <button onClick={() => counterStore.increment()}>
    {counterStore.count}
  </button>
));

effector

// model.js
import { createEvent, createStore } from 'effector';

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

// 在组件中(需配合 effector-react)
import { useUnit } from 'effector-react';

function Counter() {
  const [count, handleIncrement] = useUnit([$count, increment]);
  return <button onClick={handleIncrement}>{count}</button>;
}

xstate

// machine.js
import { createMachine, interpret } from 'xstate';

const counterMachine = createMachine({
  context: { count: 0 },
  on: { INCREMENT: { actions: 'increment' } }
}, {
  actions: {
    increment: (context) => { context.count += 1; }
  }
});

// 在组件中(需配合 @xstate/react)
import { useMachine } from '@xstate/react';

function Counter() {
  const [state, send] = useMachine(counterMachine);
  return <button onClick={() => send('INCREMENT')}>{state.context.count}</button>;
}

注意:react-query 不适用于此场景,因其设计目标是服务端状态。

⚡ 异步数据流处理

基础异步:redux-thunk

// thunk
const fetchUser = (id) => async (dispatch) => {
  dispatch({ type: 'FETCH_START' });
  try {
    const user = await api.getUser(id);
    dispatch({ type: 'FETCH_SUCCESS', payload: user });
  } catch (error) {
    dispatch({ type: 'FETCH_ERROR', payload: error });
  }
};

// 组件中
useEffect(() => {
  dispatch(fetchUser(123));
}, []);

复杂异步:redux-saga

// saga
import { call, put, takeEvery } from 'redux-saga/effects';

function* fetchUserSaga(action) {
  try {
    const user = yield call(api.getUser, action.payload.id);
    yield put({ type: 'FETCH_SUCCESS', payload: user });
  } catch (error) {
    yield put({ type: 'FETCH_ERROR', payload: error });
  }
}

function* rootSaga() {
  yield takeEvery('FETCH_REQUEST', fetchUserSaga);
}

effector 的效应(Effect)

// effect
import { createEffect } from 'effector';

const fetchUserFx = createEffect(async (id) => {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
});

// 绑定副作用
fetchUserFx.doneData.watch(user => console.log('成功:', user));
fetchUserFx.fail.watch(error => console.error('失败:', error));

// 组件中
useEffect(() => {
  fetchUserFx(123);
}, []);

react-query 的数据获取

// hooks
import { useQuery } from 'react-query';

function UserProfile({ userId }) {
  const { data, isLoading, error } = useQuery(
    ['user', userId],
    () => fetchUser(userId)
  );

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error!</div>;
  return <div>{data.name}</div>;
}

🔁 状态派生与计算

recoil 的选择器(Selector)

const fullNameSelector = selector({
  key: 'fullName',
  get: ({ get }) => {
    const firstName = get(firstNameAtom);
    const lastName = get(lastNameAtom);
    return `${firstName} ${lastName}`;
  }
});

// 组件中
const fullName = useRecoilValue(fullNameSelector);

mobx 的计算属性

class UserStore {
  firstName = 'John';
  lastName = 'Doe';
  constructor() { makeAutoObservable(this); }

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

zustand 的派生状态

const useStore = create((set, get) => ({
  firstName: 'John',
  lastName: 'Doe',
  get fullName() {
    return `${get().firstName} ${get().lastName}`;
  }
}));

effector 的派生存储

const $firstName = createStore('John');
const $lastName = createStore('Doe');
const $fullName = combine($firstName, $lastName, (first, last) => `${first} ${last}`);

🧪 调试与开发体验

  • Redux DevToolsreduxzustand(通过 middleware)、effector(通过插件)均支持时间旅行调试。
  • MobX Developer Tools:可追踪依赖关系和变更。
  • React Query Devtools:可视化查询状态、缓存和重试。
  • XState Inspector:实时查看状态机转换和上下文。

recoil 目前缺乏官方调试工具,主要依赖 React DevTools。

🌐 框架耦合度

  • React 专属recoilreact-queryzustand(虽可脱离 React 使用,但主要为 React 设计)。
  • 框架无关reduxmobxeffectorxstate 可用于任何视图层(如 Vue、Svelte),甚至无 UI 场景。

📦 中间件与扩展性

  • Redux:拥有最丰富的中间件生态(日志、持久化、undo/redo 等)。
  • Zustand:通过 subscribeWithSelectorpersist 等中间件扩展。
  • Effector:通过 forkscope 支持服务端渲染和测试隔离。
  • XState:支持自定义 action、guard、service,可集成到任意工作流引擎。

🚫 已废弃或不推荐方案

截至 2024 年,所有列出的包均处于活跃维护状态,无官方废弃声明。但需注意:

  • redux-thunkredux-saga 是 Redux 的补充,不应单独使用。
  • 若新项目无需 Redux 的严格约束,可优先考虑 zustandrecoil

📊 总结:如何选择?

场景推荐方案
服务端数据缓存、同步、后台更新react-query
大型应用,需严格可预测性和调试能力redux + redux-thunk(简单异步)或 redux-saga(复杂异步)
中型 React 应用,追求简洁和性能zustand
需要原子化状态和派生计算recoil
面向对象风格,接受可变状态mobx
复杂事件流编排,框架无关effector
明确的状态转换逻辑(如向导、游戏)xstate

💡 最终建议

不要为了用状态管理库而用。对于简单应用,React 自带的 useState + useContext 可能足够。当出现以下信号时,再考虑引入外部方案:

  • 状态更新逻辑分散在多个组件
  • 频繁的 prop drilling
  • 服务端数据与 UI 状态混杂
  • 需要跨页面共享状态

选择时,优先考虑团队熟悉度、项目规模和长期维护成本。没有银弹,只有最适合当前上下文的工具。

如何选择: effector vs mobx vs react-query vs recoil vs redux vs redux-saga vs redux-thunk vs xstate vs zustand

  • effector:

    选择 effector 如果你需要一个高性能、响应式且与框架无关的状态管理方案,特别适合需要复杂事件流编排和副作用隔离的场景。它通过声明式事件、效应和存储组合,避免了 React 上下文的性能问题,同时支持 TypeScript 深度集成。但需注意其学习曲线较陡,适合对响应式编程有经验的团队。

  • mobx:

    选择 mobx 如果你希望以面向对象的方式管理状态,并利用其自动依赖追踪实现细粒度响应更新。它非常适合中大型应用,尤其是当状态结构天然呈树形或嵌套时。配合 observer 组件,React 渲染性能表现优异。但需谨慎使用可变状态,避免破坏时间旅行调试等 Redux 式优势。

  • react-query:

    选择 react-query 如果你的应用重度依赖服务端数据(如 REST 或 GraphQL),需要自动缓存、后台刷新、请求去重和错误重试等能力。它将服务端状态与 UI 状态分离,极大简化了数据获取逻辑。但不适用于管理客户端本地状态(如表单、UI 开关等)。

  • recoil:

    选择 recoil 如果你正在构建中大型 React 应用,需要原子化状态管理以支持状态派生、异步选择器和并发模式(Concurrent Mode)兼容性。它由 Facebook 团队维护,API 设计贴近 React 生态。但项目演进较慢,社区生态不如 Redux 成熟,且仅限 React 使用。

  • redux:

    选择 redux 如果你需要一个严格可预测、可调试、可测试的全局状态容器,尤其适合需要时间旅行调试、状态持久化或复杂中间件集成的大型项目。其单向数据流和不可变更新原则有助于维护代码一致性。但样板代码较多,学习成本高,小型项目可能过度设计。

  • redux-saga:

    选择 redux-saga 如果你已在使用 Redux,且需要处理复杂的异步流程(如竞态条件、取消、重试、并行任务)。它基于 ES6 Generator 函数,提供强大的流程控制能力。但 Generator 语法对新手不友好,且调试体验不如 Promise 或 async/await 直观。

  • redux-thunk:

    选择 redux-thunk 如果你已在使用 Redux,且异步逻辑相对简单(如基本的 API 调用后 dispatch 多个 action)。它是最轻量的 Redux 异步中间件,允许在 action creator 中返回函数以访问 dispatch 和 getState。但对于复杂流程,容易导致逻辑分散和难以测试。

  • xstate:

    选择 xstate 如果你的应用包含明确的状态转换逻辑(如表单向导、游戏状态、设备控制面板),需要可视化状态机和可预测的行为。它基于 SCXML 标准,支持层级状态、并行状态和历史状态。但通用 CRUD 应用可能不需要如此严格的建模,引入成本较高。

  • zustand:

    选择 zustand 如果你希望在 React 中快速搭建一个轻量、简洁且高性能的状态管理方案,无需 Provider 包裹,支持异步操作和中间件扩展。它使用 hooks 风格,API 极简,适合中小型项目或作为 Redux 的轻量替代。但缺乏 Redux 的生态系统和工具链支持。

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