immer vs zustand vs xstate vs mobx vs react-query vs recoil vs redux vs valtio
前端状态管理与数据流解决方案对比
immerzustandxstatemobxreact-queryrecoilreduxvaltio类似的npm包:

前端状态管理与数据流解决方案对比

immermobxreact-queryrecoilreduxvaltioxstatezustand 是现代前端开发中用于处理状态管理、数据同步和应用逻辑控制的核心工具。它们各自解决不同层面的问题:reduxzustandrecoilvaltiomobx 主要用于管理客户端状态;react-query 专注于服务端状态(如 API 数据)的获取、缓存和同步;immer 提供不可变数据更新的便捷写法;而 xstate 则通过状态机模型管理复杂的状态流转逻辑。这些库可单独使用,也可组合搭配,以满足不同规模和复杂度的项目需求。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
immer33,957,68628,905913 kB481 个月前MIT
zustand24,912,46157,29695 kB61 个月前MIT
xstate4,047,07329,3132.25 MB15125 天前MIT
mobx3,131,22828,1824.35 MB855 个月前MIT
react-query1,619,26048,7472.26 MB1743 年前MIT
recoil490,16919,5242.21 MB3223 年前MIT
redux061,443290 kB432 年前MIT
valtio010,145101 kB47 天前MIT

前端状态管理全景:从数据流到状态机深度对比

在现代前端架构中,状态管理早已超越简单的“全局变量”范畴。我们面对的是客户端状态(UI 表单、主题设置)、服务端状态(API 数据、缓存)和控制流状态(向导步骤、交互流程)的混合体。本文将深入剖析 immermobxreact-queryrecoilreduxvaltioxstatezustand 这八大工具,通过真实代码揭示它们如何解决不同维度的问题。

🧠 核心定位:它们到底解决什么问题?

首先明确一点:这些库并非完全互斥,甚至不属于同一抽象层级。

  • 状态容器reduxzustandrecoilvaltiomobx 提供存储和更新客户端状态的能力。
  • 不可变更新辅助immer 不是状态容器,而是让不可变更新写起来像可变操作。
  • 服务端状态管理react-query 专治 API 数据的获取、缓存、同步和失效。
  • 状态机/流程控制xstate 用状态图建模复杂交互逻辑,不直接存数据。

下面通过具体场景展开对比。

🔁 状态更新模式:可变 vs 不可变 vs 响应式

不可变更新:Redux + Immer

传统 Redux 要求返回新对象,写起来繁琐:

// redux (without immer)
const todosReducer = (state = [], action) => {
  if (action.type === 'addTodo') {
    return [...state, { id: Date.now(), text: action.text }];
  }
  return state;
};

引入 immer 后,用可变语法实现不可变更新:

// redux + immer
import { produce } from 'immer';

const todosReducer = produce((draft, action) => {
  if (action.type === 'addTodo') {
    draft.push({ id: Date.now(), text: action.text });
  }
}, []);

响应式自动追踪:MobX

mobx 通过 observableobserver 自动追踪依赖:

// mobx
import { makeAutoObservable } from 'mobx';

class TodoStore {
  todos = [];
  constructor() {
    makeAutoObservable(this);
  }
  addTodo(text) {
    this.todos.push({ id: Date.now(), text });
  }
}

const store = new TodoStore();

// React component
import { observer } from 'mobx-react-lite';
const TodoList = observer(() => (
  <ul>
    {store.todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
  </ul>
));

Proxy 响应式:Valtio

valtio 用 Proxy 拦截读写,更轻量:

// valtio
import { proxy, useSnapshot } from 'valtio';

const state = proxy({ todos: [] });

const addTodo = (text) => {
  state.todos.push({ id: Date.now(), text });
};

// React component
const TodoList = () => {
  const snap = useSnapshot(state);
  return (
    <ul>
      {snap.todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
    </ul>
  );
};

原子化状态:Recoil

recoil 将状态拆分为原子(atom)和派生(selector):

// recoil
import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';

const todoListState = atom({
  key: 'todoList',
  default: [],
});

const todoCountState = selector({
  key: 'todoCount',
  get: ({ get }) => get(todoListState).length,
});

// Component
const TodoList = () => {
  const [todos, setTodos] = useRecoilState(todoListState);
  const count = useRecoilValue(todoCountState);
  // ...
};

简约 Hooks 状态:Zustand

zustand 用单个 hook 管理整个 store:

// zustand
import { create } from 'zustand';

const useStore = create((set) => ({
  todos: [],
  addTodo: (text) =>
    set((state) => ({
      todos: [...state.todos, { id: Date.now(), text }],
    })),
}));

// Component
const TodoList = () => {
  const { todos, addTodo } = useStore();
  // ...
};

🌐 服务端状态:React Query 的专属领域

以上工具都聚焦客户端状态,而 react-query 处理服务端状态

// react-query
import { useQuery, useMutation, QueryClient } from 'react-query';

const queryClient = new QueryClient();

// Fetching
const { data: todos, isLoading } = useQuery('todos', () =>
  fetch('/api/todos').then(res => res.json())
);

// Mutating
const mutation = useMutation(
  (newTodo) => fetch('/api/todos', { method: 'POST', body: JSON.stringify(newTodo) }),
  {
    onSuccess: () => {
      // 自动刷新 'todos' 查询
      queryClient.invalidateQueries('todos');
    },
  }
);

关键区别:react-query 的数据来自服务端,有 TTL、后台刷新、离线支持等特性,而客户端状态库的数据是用户交互产生的临时状态。

🤖 复杂流程控制:XState 的状态机

当状态不是简单数据,而是行为模式时,xstate 登场:

// xstate
import { createMachine, interpret } from 'xstate';

const formMachine = createMachine({
  id: 'form',
  initial: 'idle',
  states: {
    idle: {
      on: { SUBMIT: 'submitting' }
    },
    submitting: {
      invoke: {
        src: 'submitForm',
        onDone: 'success',
        onError: 'failure'
      }
    },
    success: {},
    failure: {
      on: { RETRY: 'submitting' }
    }
  }
});

const service = interpret(formMachine).start();
service.send('SUBMIT');

xstate 不存表单数据,只管理“当前处于提交中还是失败”这类控制状态。

🧪 真实场景对比:Todo 应用的不同实现

假设我们要实现一个带过滤、计数的 Todo 应用。

Redux + Immer

// Store
import { createSlice } from '@reduxjs/toolkit'; // 内置 immer

const todosSlice = createSlice({
  name: 'todos',
  initialState: { list: [], filter: 'all' },
  reducers: {
    addTodo: (state, action) => {
      state.list.push({ id: Date.now(), text: action.payload, done: false });
    },
    setFilter: (state, action) => {
      state.filter = action.payload;
    }
  }
});

// Component
import { useSelector, useDispatch } from 'react-redux';
const TodoApp = () => {
  const { list, filter } = useSelector(state => state.todos);
  const dispatch = useDispatch();
  // ...
};

Zustand

// Store
const useTodoStore = create((set, get) => ({
  list: [],
  filter: 'all',
  addTodo: (text) => set(state => ({
    list: [...state.list, { id: Date.now(), text, done: false }]
  })),
  setFilter: (filter) => set({ filter }),
  filteredList: () => {
    const { list, filter } = get();
    return filter === 'all' ? list : list.filter(t => t.done === (filter === 'done'));
  }
}));

// Component
const TodoApp = () => {
  const { filteredList, addTodo, setFilter } = useTodoStore();
  const todos = filteredList();
  // ...
};

React Query(仅服务端数据)

// 注意:这里假设所有 Todo 存在服务端
const TodoApp = () => {
  const { data: todos = [] } = useQuery('todos', fetchTodos);
  const mutation = useMutation(addTodo, {
    onSuccess: () => queryClient.invalidateQueries('todos')
  });

  // 客户端过滤状态仍需 useState 或其他库
  const [filter, setFilter] = useState('all');
  const filtered = useMemo(() => /* ... */, [todos, filter]);
};

⚖️ 关键权衡:何时用哪个?

团队熟悉度与心智模型

  • Redux:要求理解 action/reducer/store,但概念清晰,适合严格规范的团队。
  • MobX/Valtio:响应式模型直观,但“魔法”较多,新手可能困惑为何修改会触发重渲染。
  • Zustand/Recoil:Hooks 友好,学习曲线平缓,适合 React 原生开发者。
  • XState:需学习状态机理论,但一旦掌握,复杂流程 bug 大幅减少。

性能与重渲染

  • Redux:默认全量重渲染,需 useSelector 优化或 createSelector
  • MobX/Valtio:细粒度更新,只重渲染用到变化字段的组件。
  • Recoil:原子化订阅,自动优化依赖。
  • Zustand:默认整个 store 变化会触发所有订阅者,但可通过 subscribe 手动优化。

调试与工具链

  • Redux:DevTools 支持时间旅行、action 日志,业界最强。
  • MobX:有独立 DevTools,但不如 Redux 成熟。
  • Recoil:内置 DevTools,支持时间旅行。
  • XState:可视化状态图工具(XState Inspector)是杀手级特性。

🛠️ 组合使用:没有银弹

现实中常组合使用:

  • React Query + Zustandreact-query 管 API 数据,zustand 管 UI 状态(如模态框开关)。
  • Redux + Immerimmer 作为 Redux Toolkit 的一部分,简化 reducer 编写。
  • XState + React Queryxstate 控制表单步骤,react-query 提交数据并处理加载/错误状态。
// Example: XState for flow, React Query for data
const machine = createMachine({
  // ...
  invoking submitForm which uses react-query mutation
});

// In component
const { mutate, isLoading } = useMutation(submitData);
const send = useService(machine)[1];

const handleSubmit = () => {
  send('SUBMIT');
  mutate(formData, {
    onSuccess: () => send('SUCCESS'),
    onError: () => send('FAILURE')
  });
};

📊 总结:核心差异速查表

类型响应式不可变服务端状态状态机适用场景
immer更新辅助简化不可变更新
mobx客户端状态响应式中大型应用
react-query服务端状态N/AAPI 数据管理
recoil客户端状态原子化复杂状态
redux客户端状态可预测大型应用
valtio客户端状态轻量响应式原型
xstate流程控制N/A复杂交互逻辑
zustand客户端状态简约 Hooks 状态

💡 最终建议

  • 新项目起步:优先考虑 zustand(客户端状态) + react-query(服务端状态),兼顾简洁与功能。
  • 遗留 Redux 项目:用 @reduxjs/toolkit(含 immer)现代化改造,而非重写。
  • 复杂交互流程:无论用什么状态库,引入 xstate 管理控制流。
  • 性能敏感场景mobxvaltio 的细粒度更新可能优于 Redux 的全量检查。

记住:状态管理的目标是降低复杂度,而非增加技术栈。选最匹配你问题域的工具,必要时组合使用,但避免过度设计。

如何选择: immer vs zustand vs xstate vs mobx vs react-query vs recoil vs redux vs valtio

  • immer:

    选择 immer 如果你希望在保持不可变性的同时,用熟悉的可变语法(如直接赋值)来更新状态。它特别适合与 Redux 等要求不可变更新的库配合使用,能显著减少样板代码,但注意它本身不提供状态容器,仅处理更新逻辑。

  • zustand:

    选择 zustand 如果你想要一个轻量、无样板、Hooks 友好的全局状态方案,无需 Provider 包裹,且支持中间件和持久化。它避免了 React Context 的重渲染问题,适合大多数中小型 React 应用,是 Redux 的现代化替代品之一。

  • xstate:

    选择 xstate 如果你的应用涉及复杂的状态流转(如向导、表单验证、游戏逻辑),需要显式定义状态机/状态图以提升可预测性和可测试性。它不直接管理数据,而是控制状态之间的转换规则,适合业务逻辑驱动而非数据驱动的场景。

  • mobx:

    选择 mobx 如果你需要一个响应式、细粒度自动追踪依赖的状态系统,且团队接受基于装饰器或函数包装的响应式编程模型。它适合中大型应用中需要高性能局部更新的场景,但需注意其隐式依赖追踪可能增加调试难度。

  • react-query:

    选择 react-query 如果你的应用重度依赖服务端数据(如 REST 或 GraphQL API),需要自动缓存、后台刷新、请求去重、错误重试等能力。它专为服务端状态设计,不应与客户端 UI 状态混用,是现代数据获取层的事实标准之一。

  • recoil:

    选择 recoil 如果你使用 React 并希望获得原子化状态管理、派生状态自动缓存、以及时间旅行调试等能力,同时偏好声明式 API。它由 Meta 维护,适合复杂状态依赖关系的场景,但需注意其仍处于活跃演进阶段,API 可能调整。

  • redux:

    选择 redux 如果你需要一个严格、可预测、高度可调试的全局状态容器,且项目已存在大量 Redux 生态(如 middleware、devtools)。尽管样板代码较多,但结合 Redux Toolkit 后已大幅简化,适合对状态一致性要求极高的大型应用。

  • valtio:

    选择 valtio 如果你追求极简 API 和 Proxy 驱动的响应式状态,希望用接近原生对象的方式编写状态逻辑,同时享受 React 的自动重渲染。它比 MobX 更轻量,适合中小型项目或快速原型开发,但生态和工具链支持不如 Redux 成熟。

immer的README

Immer

npm Build Status Coverage Status code style: prettier OpenCollective OpenCollective Gitpod Ready-to-Code

Create the next immutable state tree by simply modifying the current tree

Winner of the "Breakthrough of the year" React open source award and "Most impactful contribution" JavaScript open source award in 2019

Contribute using one-click online setup

You can use Gitpod (a free online VSCode like IDE) for contributing online. With a single click it will launch a workspace and automatically:

  • clone the immer repo.
  • install the dependencies.
  • run yarn run start.

so that you can start coding straight away.

Open in Gitpod

Documentation

The documentation of this package is hosted at https://immerjs.github.io/immer/

Support

Did Immer make a difference to your project? Join the open collective at https://opencollective.com/immer!

Release notes

https://github.com/immerjs/immer/releases