immer、mobx、react-query、recoil、redux、valtio、xstate 和 zustand 是现代前端开发中用于处理状态管理、数据同步和应用逻辑控制的核心工具。它们各自解决不同层面的问题:redux、zustand、recoil、valtio 和 mobx 主要用于管理客户端状态;react-query 专注于服务端状态(如 API 数据)的获取、缓存和同步;immer 提供不可变数据更新的便捷写法;而 xstate 则通过状态机模型管理复杂的状态流转逻辑。这些库可单独使用,也可组合搭配,以满足不同规模和复杂度的项目需求。
在现代前端架构中,状态管理早已超越简单的“全局变量”范畴。我们面对的是客户端状态(UI 表单、主题设置)、服务端状态(API 数据、缓存)和控制流状态(向导步骤、交互流程)的混合体。本文将深入剖析 immer、mobx、react-query、recoil、redux、valtio、xstate 和 zustand 这八大工具,通过真实代码揭示它们如何解决不同维度的问题。
首先明确一点:这些库并非完全互斥,甚至不属于同一抽象层级。
redux、zustand、recoil、valtio、mobx 提供存储和更新客户端状态的能力。immer 不是状态容器,而是让不可变更新写起来像可变操作。react-query 专治 API 数据的获取、缓存、同步和失效。xstate 用状态图建模复杂交互逻辑,不直接存数据。下面通过具体场景展开对比。
传统 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 通过 observable 和 observer 自动追踪依赖:
// 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>
));
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 将状态拆分为原子(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);
// ...
};
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
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
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 应用。
// 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();
// ...
};
// 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();
// ...
};
// 注意:这里假设所有 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]);
};
useSelector 优化或 createSelector。subscribe 手动优化。现实中常组合使用:
react-query 管 API 数据,zustand 管 UI 状态(如模态框开关)。immer 作为 Redux Toolkit 的一部分,简化 reducer 编写。xstate 控制表单步骤,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/A | ✅ | ❌ | API 数据管理 |
recoil | 客户端状态 | ✅ | ✅ | ❌ | ❌ | 原子化复杂状态 |
redux | 客户端状态 | ❌ | ✅ | ❌ | ❌ | 可预测大型应用 |
valtio | 客户端状态 | ✅ | ❌ | ❌ | ❌ | 轻量响应式原型 |
xstate | 流程控制 | ✅ | N/A | ❌ | ✅ | 复杂交互逻辑 |
zustand | 客户端状态 | ❌ | ✅ | ❌ | ❌ | 简约 Hooks 状态 |
zustand(客户端状态) + react-query(服务端状态),兼顾简洁与功能。@reduxjs/toolkit(含 immer)现代化改造,而非重写。xstate 管理控制流。mobx 或 valtio 的细粒度更新可能优于 Redux 的全量检查。记住:状态管理的目标是降低复杂度,而非增加技术栈。选最匹配你问题域的工具,必要时组合使用,但避免过度设计。
选择 immer 如果你希望在保持不可变性的同时,用熟悉的可变语法(如直接赋值)来更新状态。它特别适合与 Redux 等要求不可变更新的库配合使用,能显著减少样板代码,但注意它本身不提供状态容器,仅处理更新逻辑。
选择 zustand 如果你想要一个轻量、无样板、Hooks 友好的全局状态方案,无需 Provider 包裹,且支持中间件和持久化。它避免了 React Context 的重渲染问题,适合大多数中小型 React 应用,是 Redux 的现代化替代品之一。
选择 xstate 如果你的应用涉及复杂的状态流转(如向导、表单验证、游戏逻辑),需要显式定义状态机/状态图以提升可预测性和可测试性。它不直接管理数据,而是控制状态之间的转换规则,适合业务逻辑驱动而非数据驱动的场景。
选择 mobx 如果你需要一个响应式、细粒度自动追踪依赖的状态系统,且团队接受基于装饰器或函数包装的响应式编程模型。它适合中大型应用中需要高性能局部更新的场景,但需注意其隐式依赖追踪可能增加调试难度。
选择 react-query 如果你的应用重度依赖服务端数据(如 REST 或 GraphQL API),需要自动缓存、后台刷新、请求去重、错误重试等能力。它专为服务端状态设计,不应与客户端 UI 状态混用,是现代数据获取层的事实标准之一。
选择 recoil 如果你使用 React 并希望获得原子化状态管理、派生状态自动缓存、以及时间旅行调试等能力,同时偏好声明式 API。它由 Meta 维护,适合复杂状态依赖关系的场景,但需注意其仍处于活跃演进阶段,API 可能调整。
选择 redux 如果你需要一个严格、可预测、高度可调试的全局状态容器,且项目已存在大量 Redux 生态(如 middleware、devtools)。尽管样板代码较多,但结合 Redux Toolkit 后已大幅简化,适合对状态一致性要求极高的大型应用。
选择 valtio 如果你追求极简 API 和 Proxy 驱动的响应式状态,希望用接近原生对象的方式编写状态逻辑,同时享受 React 的自动重渲染。它比 MobX 更轻量,适合中小型项目或快速原型开发,但生态和工具链支持不如 Redux 成熟。
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
You can use Gitpod (a free online VSCode like IDE) for contributing online. With a single click it will launch a workspace and automatically:
yarn run start.so that you can start coding straight away.
The documentation of this package is hosted at https://immerjs.github.io/immer/
Did Immer make a difference to your project? Join the open collective at https://opencollective.com/immer!