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 则基于状态机理论,适用于具有明确状态转换逻辑的交互场景。
在现代前端开发中,状态管理早已超越简单的 useState。面对复杂交互、服务端数据同步、跨组件通信等挑战,开发者需要在多种范式中做出权衡:是选择可预测的单向数据流,还是响应式的自动追踪?是聚焦客户端状态,还是统一服务端缓存?本文将从真实工程视角,深入比较九种主流方案 —— effector、mobx、react-query、recoil、redux、redux-saga、redux-thunk、xstate 和 zustand。
首先区分两类状态:
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、zustand(通过 middleware)、effector(通过插件)均支持时间旅行调试。recoil 目前缺乏官方调试工具,主要依赖 React DevTools。
recoil、react-query、zustand(虽可脱离 React 使用,但主要为 React 设计)。redux、mobx、effector、xstate 可用于任何视图层(如 Vue、Svelte),甚至无 UI 场景。subscribeWithSelector、persist 等中间件扩展。fork、scope 支持服务端渲染和测试隔离。截至 2024 年,所有列出的包均处于活跃维护状态,无官方废弃声明。但需注意:
redux-thunk 和 redux-saga 是 Redux 的补充,不应单独使用。zustand 或 recoil。| 场景 | 推荐方案 |
|---|---|
| 服务端数据缓存、同步、后台更新 | react-query |
| 大型应用,需严格可预测性和调试能力 | redux + redux-thunk(简单异步)或 redux-saga(复杂异步) |
| 中型 React 应用,追求简洁和性能 | zustand |
| 需要原子化状态和派生计算 | recoil |
| 面向对象风格,接受可变状态 | mobx |
| 复杂事件流编排,框架无关 | effector |
| 明确的状态转换逻辑(如向导、游戏) | xstate |
不要为了用状态管理库而用。对于简单应用,React 自带的 useState + useContext 可能足够。当出现以下信号时,再考虑引入外部方案:
选择时,优先考虑团队熟悉度、项目规模和长期维护成本。没有银弹,只有最适合当前上下文的工具。
选择 effector 如果你需要一个高性能、响应式且与框架无关的状态管理方案,特别适合需要复杂事件流编排和副作用隔离的场景。它通过声明式事件、效应和存储组合,避免了 React 上下文的性能问题,同时支持 TypeScript 深度集成。但需注意其学习曲线较陡,适合对响应式编程有经验的团队。
选择 mobx 如果你希望以面向对象的方式管理状态,并利用其自动依赖追踪实现细粒度响应更新。它非常适合中大型应用,尤其是当状态结构天然呈树形或嵌套时。配合 observer 组件,React 渲染性能表现优异。但需谨慎使用可变状态,避免破坏时间旅行调试等 Redux 式优势。
选择 react-query 如果你的应用重度依赖服务端数据(如 REST 或 GraphQL),需要自动缓存、后台刷新、请求去重和错误重试等能力。它将服务端状态与 UI 状态分离,极大简化了数据获取逻辑。但不适用于管理客户端本地状态(如表单、UI 开关等)。
选择 recoil 如果你正在构建中大型 React 应用,需要原子化状态管理以支持状态派生、异步选择器和并发模式(Concurrent Mode)兼容性。它由 Facebook 团队维护,API 设计贴近 React 生态。但项目演进较慢,社区生态不如 Redux 成熟,且仅限 React 使用。
选择 redux 如果你需要一个严格可预测、可调试、可测试的全局状态容器,尤其适合需要时间旅行调试、状态持久化或复杂中间件集成的大型项目。其单向数据流和不可变更新原则有助于维护代码一致性。但样板代码较多,学习成本高,小型项目可能过度设计。
选择 redux-saga 如果你已在使用 Redux,且需要处理复杂的异步流程(如竞态条件、取消、重试、并行任务)。它基于 ES6 Generator 函数,提供强大的流程控制能力。但 Generator 语法对新手不友好,且调试体验不如 Promise 或 async/await 直观。
选择 redux-thunk 如果你已在使用 Redux,且异步逻辑相对简单(如基本的 API 调用后 dispatch 多个 action)。它是最轻量的 Redux 异步中间件,允许在 action creator 中返回函数以访问 dispatch 和 getState。但对于复杂流程,容易导致逻辑分散和难以测试。
选择 xstate 如果你的应用包含明确的状态转换逻辑(如表单向导、游戏状态、设备控制面板),需要可视化状态机和可预测的行为。它基于 SCXML 标准,支持层级状态、并行状态和历史状态。但通用 CRUD 应用可能不需要如此严格的建模,引入成本较高。
选择 zustand 如果你希望在 React 中快速搭建一个轻量、简洁且高性能的状态管理方案,无需 Provider 包裹,支持异步操作和中间件扩展。它使用 hooks 风格,API 极简,适合中小型项目或作为 Redux 的轻量替代。但缺乏 Redux 的生态系统和工具链支持。
Business logic with ease
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.
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
For additional information, guides and api reference visit our documentation site
effector to your project's home pageYou can try effector with online playground
Code sharing, Typescript and react supported out of the box. Playground repository
Use effector-logger for printing updates to console, displaying current store values with ui or connecting application to familiar redux devtools
Your support allows us to improve the developer experience 🧡.