immer vs zustand vs redux vs valtio vs recoil vs mobx
前端状态管理库深度对比:Immer、MobX、Recoil、Redux、Valtio 与 Zustand
immerzustandreduxvaltiorecoilmobx类似的npm包:

前端状态管理库深度对比:Immer、MobX、Recoil、Redux、Valtio 与 Zustand

immermobxrecoilreduxvaltiozustand 都是用于管理 JavaScript 应用(尤其是 React)中状态的流行库,但它们在设计理念、API 风格和适用场景上有显著差异。redux 是最早广泛采用的 Flux 架构实现,强调不可变性和单一数据源;immer 并非完整状态管理方案,而是通过“可变写法生成不可变更新”的工具,常与其他库配合使用;mobx 基于响应式编程模型,允许直接修改状态并自动追踪依赖;recoil 是 Facebook 推出的原子化状态管理库,支持细粒度订阅和异步数据流;zustandvaltio 则代表了更轻量、更简洁的现代方案,前者基于单一 store 和 hooks,后者结合 proxy 与响应式更新,提供类似 mutable 的开发体验。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
immer30,627,93228,892913 kB4618 天前MIT
zustand23,295,96257,17895 kB41 个月前MIT
redux23,075,69861,439290 kB432 年前MIT
valtio1,177,28010,129101 kB22 个月前MIT
recoil464,45819,5282.21 MB3223 年前MIT
mobx028,1824.35 MB855 个月前MIT

前端状态管理六剑客:Immer、MobX、Recoil、Redux、Valtio 与 Zustand 深度剖析

在 React 生态中,状态管理方案百花齐放。本文将从核心理念、API 设计、性能特征和适用场景出发,对比 immermobxrecoilreduxvaltiozustand,帮助你在真实项目中做出明智选择。

🧠 核心理念:不可变 vs 响应式 vs 原子化

redux:不可变 + 单一数据源

redux 遵循 Flux 架构,强调状态不可变、单向数据流和纯函数 reducer。所有状态变更必须通过 dispatch action 触发,确保可预测性和可调试性。

// redux
const initialState = { count: 0 };

function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    default:
      return state;
  }
}

// 使用
const dispatch = useDispatch();
dispatch({ type: 'increment' });

immer:可变写法生成不可变更新

immer 不是状态管理库,而是通过 Proxy 拦截“可变”操作,自动生成符合不可变规范的新状态。常与 Redux 或 Zustand 结合使用。

// immer + redux toolkit 示例
import { createSlice } from '@reduxjs/toolkit';

const slice = createSlice({
  name: 'counter',
  initialState: { count: 0 },
  reducers: {
    increment: (state) => {
      state.count += 1; // 看似可变,实则生成新对象
    }
  }
});

mobx:响应式 + 可变状态

mobx 将状态标记为 observable,当组件读取这些状态时自动建立依赖关系。直接修改状态即可触发相关组件更新。

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

class CounterStore {
  count = 0;
  constructor() {
    makeAutoObservable(this);
  }
  increment = () => {
    this.count += 1; // 直接修改
  };
}

const store = new CounterStore();

// 在组件中
import { observer } from 'mobx-react-lite';
const Counter = observer(() => <div>{store.count}</div>);

recoil:原子化状态 + 派生状态

recoil 将状态拆分为 atom(基础状态)和 selector(派生状态),支持异步 selector 和 Suspense。

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

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

const doubleCountSelector = selector({
  key: 'doubleCount',
  get: ({ get }) => get(countAtom) * 2
});

// 组件中
const [count, setCount] = useRecoilState(countAtom);
const double = useRecoilValue(doubleCountSelector);

zustand:轻量 store + hooks

zustand 提供一个全局 store,通过 hooks 访问状态,不依赖 React Context,避免了 Context 导致的不必要重渲染。

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

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

// 组件中
const { count, increment } = useStore();

valtio:Proxy 驱动的响应式状态

valtio 使用 Proxy 包装状态对象,组件通过 useSnapshot 订阅状态变化,自动进行细粒度更新。

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

const state = proxy({ count: 0 });

// 组件中
const snap = useSnapshot(state);
// 直接修改原始 state
const handleClick = () => ++state.count;

⚡ 性能与重渲染行为

细粒度更新能力

  • mobxvaltio:基于依赖追踪,仅当组件实际使用的状态变更时才重渲染。
  • recoil:atom 和 selector 天然支持细粒度订阅。
  • zustand:默认整个 store 变更会触发所有订阅者,但可通过 subscribeWithSelector 中间件实现选择性订阅。
  • redux:需配合 React.memocreateSelector 手动优化,否则容易导致无关组件重渲染。

示例:避免无关重渲染

// zustand:选择性订阅
const useCount = (state) => state.count;
const CountDisplay = () => {
  const count = useStore(useCount); // 仅当 count 变更时更新
  return <div>{count}</div>;
};

// valtio:自动细粒度
const Counter = () => {
  const snap = useSnapshot(state);
  return <div>{snap.count}</div>; // 仅 count 变更时更新
};

// mobx:observer 自动追踪
const Counter = observer(() => <div>{store.count}</div>);

// recoil:天然隔离
const CountDisplay = () => {
  const count = useRecoilValue(countAtom);
  return <div>{count}</div>;
};

🔌 异步与副作用处理

redux:中间件(如 Redux Thunk / Saga)

// redux-thunk
const fetchUser = (id) => async (dispatch) => {
  const user = await api.getUser(id);
  dispatch({ type: 'setUser', payload: user });
};

mobx:action 中直接使用 async/await

// mobx
fetchUser = async (id) => {
  const user = await api.getUser(id);
  this.user = user; // 直接赋值
};

recoil:异步 selector + Suspense

// recoil
const userQuery = selectorFamily({
  key: 'userQuery',
  get: (id) => async () => {
    const res = await fetch(`/api/user/${id}`);
    return res.json();
  }
});

// 组件中配合 Suspense
const User = ({ id }) => {
  const user = useRecoilValue(userQuery(id));
  return <div>{user.name}</div>;
};

zustand:直接在 action 中处理异步

// zustand
const useStore = create((set) => ({
  user: null,
  fetchUser: async (id) => {
    const user = await api.getUser(id);
    set({ user });
  }
}));

valtio:直接修改状态

// valtio
const fetchUser = async (id) => {
  const user = await api.getUser(id);
  state.user = user; // 直接赋值
};

📦 状态结构与嵌套更新

处理深层嵌套对象时,各库表现差异明显:

// 假设状态结构:{ user: { profile: { name: '...' } } }

// redux:需手动展开
{
  ...state,
  user: {
    ...state.user,
    profile: {
      ...state.user.profile,
      name: 'new name'
    }
  }
}

// immer:直接赋值
state.user.profile.name = 'new name';

// mobx / valtio:直接赋值
store.user.profile.name = 'new name';
state.user.profile.name = 'new name';

// zustand:通常配合 immer
set(produce((draft) => {
  draft.user.profile.name = 'new name';
}));

// recoil:需替换整个 atom 或使用 updater 函数
set(userAtom, (prev) => ({
  ...prev,
  profile: { ...prev.profile, name: 'new name' }
}));

🧪 调试与开发体验

  • redux:拥有最强大的 DevTools,支持时间旅行、action 日志、状态快照等。
  • mobx:提供 mobx-devtools,可查看依赖树和变更记录。
  • recoil:DevTools 支持查看 atoms/selectors 状态和依赖关系。
  • zustand / valtio:调试能力较弱,主要依赖浏览器原生工具。
  • immer:作为工具库,调试体验取决于主状态管理方案。

🛠️ 中间件与扩展性

  • redux:中间件生态极其丰富(logger、thunk、saga、persistence 等)。
  • zustand:支持中间件(如 devtoolspersistimmer)。
  • mobx:通过 intercept / observe / autorun 实现自定义逻辑。
  • recoil:通过 atom effects 实现副作用(如持久化)。
  • valtio:扩展性较弱,但可通过 subscribe 监听状态变化。

📊 总结:如何选择?

场景推荐方案
大型企业级应用,需严格可预测性和调试能力redux + redux-toolkit + immer
中大型应用,追求高性能和响应式开发体验mobx
React 应用,需细粒度更新和异步数据流集成recoil
中小型应用,希望轻量、简单、无样板代码zustandvaltio
已有不可变状态方案但更新逻辑复杂引入 immer 简化写法

💡 最终建议

  • 如果你是 Redux 老用户,继续使用 @reduxjs/toolkit(内置 Immer),它大幅简化了样板代码。
  • 如果你 讨厌样板代码 且项目规模适中,zustand 是最平衡的选择。
  • 如果你 喜欢直接修改对象 且能接受响应式“魔法”,mobxvaltio 会让你写得飞快。
  • 如果你 重度使用 React Suspense 或需要复杂派生状态,recoil 值得尝试。
  • 永远不要单独使用 immer —— 它是增强工具,不是状态管理方案。

记住:没有“最好”的库,只有“最适合当前项目上下文”的选择。

如何选择: immer vs zustand vs redux vs valtio vs recoil vs mobx

  • immer:

    选择 immer 如果你需要在保持不可变性语义的同时,用直观的“可变”语法编写状态更新逻辑。它不是独立的状态管理库,而是作为辅助工具与 Redux、Zustand 等搭配使用,特别适合处理深层嵌套对象的更新,避免繁琐的展开操作。如果你的项目已使用不可变状态但代码冗长,引入 Immer 能显著提升可读性和开发效率。

  • zustand:

    选择 zustand 如果你需要一个轻量、无样板、基于 hooks 的状态管理方案,且不依赖 React Context。它提供单一 store、中间件支持和良好的 TypeScript 集成,适合大多数 React 应用,尤其是希望避免 Context 导致的不必要重渲染的场景。其 API 简洁直观,学习成本低,是现代 React 项目中 Redux 的轻量替代品。

  • redux:

    选择 redux 如果你需要严格的单向数据流、完善的开发工具(如时间旅行调试)、成熟的中间件生态(如 Redux Toolkit、RTK Query),或团队已有相关经验。它适合大型、长期维护的项目,尤其是对状态变更可预测性和可测试性要求极高的场景,但样板代码较多,学习曲线较陡,建议搭配 Redux Toolkit 使用以简化开发。

  • valtio:

    选择 valtio 如果你追求极简 API 和类似 mutable 的开发体验,同时希望自动获得响应式更新和 React 组件的高效重渲染。它基于 Proxy 实现,状态即普通对象,适合中小型项目或快速原型开发,能显著减少模板代码,但对深层嵌套结构的性能优化不如 MobX 成熟,且生态相对较小。

  • recoil:

    选择 recoil 如果你使用 React 并希望获得细粒度的状态订阅能力、内置的异步数据加载支持(如 Suspense 集成)以及原子化状态组合。它适合需要高性能渲染(避免无关组件重渲染)和复杂派生状态计算的应用,但需接受其相对较新的生态和对 React 特性(如 Concurrent Mode)的强依赖。

  • mobx:

    选择 mobx 如果你偏好面向对象的响应式编程模型,希望直接修改状态属性而无需手动触发更新,并且项目对运行时性能要求较高(得益于其细粒度依赖追踪)。它适合中大型应用,尤其是状态结构复杂、需要频繁局部更新的场景,但需注意其“魔法”机制可能增加调试难度,团队需统一理解其响应式规则。

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