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

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

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

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
redux42,348,80861,509290 kB493 年前MIT
recoil483,45919,4312.21 MB3203 年前MIT
effector144,2774,8491.59 MB1591 年前MIT
mobx028,2064.8 MB577 天前MIT
react-query050,2112.26 MB1254 年前MIT
redux-saga0-36.8 kB-1 个月前MIT
redux-thunk017,67826.8 kB23 年前MIT
xstate030,0612.29 MB1221 天前MIT
zustand058,61295.2 kB514 天前MIT

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

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

🧠 核心理念与适用边界

客户端状态 vs 服务端状态

首先区分两类状态:

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

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

编程范式差异

  • 命令式/可变:mobx 允许直接修改状态,依赖自动追踪。
  • 声明式/不可变:redux、zustand、recoil 要求通过 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 DevTools:redux、zustand(通过 middleware)、effector(通过插件)均支持时间旅行调试。
  • MobX Developer Tools:可追踪依赖关系和变更。
  • React Query Devtools:可视化查询状态、缓存和重试。
  • XState Inspector:实时查看状态机转换和上下文。

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

🌐 框架耦合度

  • React 专属:recoil、react-query、zustand(虽可脱离 React 使用,但主要为 React 设计)。
  • 框架无关:redux、mobx、effector、xstate 可用于任何视图层(如 Vue、Svelte),甚至无 UI 场景。

📦 中间件与扩展性

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

🚫 已废弃或不推荐方案

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

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

📊 总结:如何选择?

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

💡 最终建议

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

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

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

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

  • redux:

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

  • recoil:

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

  • effector:

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

  • mobx:

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

  • react-query:

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

  • 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 的生态系统和工具链支持。

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