React 状态管理终极指南:Zustand vs Jotai vs Redux 实战对比

By | 2026年7月10日

前言

React 状态管理一直是前端开发者绕不开的话题。从早期的 Redux 一统江湖,到如今 Zustand、Jotai 等轻量方案崛起,开发者面临的选择越来越多。但选择多了,反而容易陷入「选择困难症」。

本文将带你亲手实现一个电商购物车功能,分别用 Zustand、Jotai 和 Redux 三种方案完成,并对比它们的代码量、性能、可维护性及学习曲线。读完本文,你将能根据项目需求做出最合适的选型。

项目准备

我们先创建一个 React 项目(使用 Vite + TypeScript):


npm create vite@latest state-compare -- --template react-ts
cd state-compare
npm install

然后安装三个状态管理库:


npm install zustand jotai @reduxjs/toolkit react-redux

场景:购物车

我们需要实现的功能:

  • 商品列表(从 API 获取)
  • 添加商品到购物车
  • 购物车数量加减
  • 删除商品
  • 计算总价

我们将在 src/App.tsx 中统一渲染,但状态管理逻辑分别放在不同文件夹中。

方案一:Zustand

Zustand 是一个极简的状态管理库,核心是一个 hook 式的 store。

1. 创建 Store

创建 src/zustand/store.ts


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

interface CartItem {
  id: number;
  name: string;
  price: number;
  quantity: number;
}

interface CartState {
  items: CartItem[];
  addItem: (item: Omit<CartItem, 'quantity'>) => void;
  removeItem: (id: number) => void;
  updateQuantity: (id: number, quantity: number) => void;
  clearCart: () => void;
  total: () => number;
}

export const useCartStore = create<CartState>()(
  persist(
    (set, get) => ({
      items: [],
      addItem: (item) => {
        const items = get().items;
        const existing = items.find(i => i.id === item.id);
        if (existing) {
          set({ items: items.map(i => i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i) });
        } else {
          set({ items: [...items, { ...item, quantity: 1 }] });
        }
      },
      removeItem: (id) => set((state) => ({ items: state.items.filter(i => i.id !== id) })),
      updateQuantity: (id, quantity) => set((state) => ({
        items: state.items.map(i => i.id === id ? { ...i, quantity: Math.max(1, quantity) } : i)
      })),
      clearCart: () => set({ items: [] }),
      total: () => get().items.reduce((sum, item) => sum + item.price * item.quantity, 0),
    }),
    { name: 'cart-storage' }
  )
);

> 💡 使用 persist 中间件可以自动将购物车数据持久化到 localStorage。

2. 在组件中使用

创建 src/zustand/Cart.tsx


import { useCartStore } from './store';

export function Cart() {
  const { items, addItem, removeItem, updateQuantity, clearCart, total } = useCartStore();

  return (
    <div>
      <h2>Zustand 购物车</h2>
      {items.length === 0 ? <p>购物车为空</p> : (
        <ul>
          {items.map(item => (
            <li key={item.id}>
              {item.name} - ${item.price} x {item.quantity}
              <button onClick={() => updateQuantity(item.id, item.quantity - 1)}>-</button>
              <button onClick={() => updateQuantity(item.id, item.quantity + 1)}>+</button>
              <button onClick={() => removeItem(item.id)}>删除</button>
            </li>
          ))}
        </ul>
      )}
      <p>总价: ${total()}</p>
      <button onClick={clearCart}>清空购物车</button>
    </div>
  );
}

Zustand 的优势在于:API 简洁,无需 Provider 包裹,直接在任何组件中调用 hook 即可。性能方面,Zustand 默认使用 Object.is 进行浅比较,只在相关状态变化时重新渲染。

方案二:Jotai

Jotai 采用原子化状态管理,每个状态是一个独立的 atom,通过组合实现复杂逻辑。

1. 创建 atoms

创建 src/jotai/store.ts


import { atom } from 'jotai';

export interface CartItem {
  id: number;
  name: string;
  price: number;
  quantity: number;
}

export const cartItemsAtom = atom<CartItem[]>([]);

export const addItemAtom = atom(
  null,
  (get, set, item: Omit<CartItem, 'quantity'>) => {
    const items = get(cartItemsAtom);
    const existing = items.find(i => i.id === item.id);
    if (existing) {
      set(cartItemsAtom, items.map(i => i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i));
    } else {
      set(cartItemsAtom, [...items, { ...item, quantity: 1 }]);
    }
  }
);

export const removeItemAtom = atom(
  null,
  (get, set, id: number) => {
    set(cartItemsAtom, get(cartItemsAtom).filter(i => i.id !== id));
  }
);

export const updateQuantityAtom = atom(
  null,
  (get, set, { id, quantity }: { id: number; quantity: number }) => {
    set(cartItemsAtom, get(cartItemsAtom).map(i => i.id === id ? { ...i, quantity: Math.max(1, quantity) } : i));
  }
);

export const totalAtom = atom((get) => {
  const items = get(cartItemsAtom);
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
});

> 💡 Jotai 的 atom 可以定义读写逻辑,派生 atom(如 totalAtom)会自动缓存,只有依赖变化时才重新计算。

2. 在组件中使用

创建 src/jotai/Cart.tsx


import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import { cartItemsAtom, addItemAtom, removeItemAtom, updateQuantityAtom, totalAtom } from './store';

export function Cart() {
  const items = useAtomValue(cartItemsAtom);
  const addItem = useSetAtom(addItemAtom);
  const removeItem = useSetAtom(removeItemAtom);
  const updateQuantity = useSetAtom(updateQuantityAtom);
  const total = useAtomValue(totalAtom);

  return (
    <div>
      <h2>Jotai 购物车</h2>
      {items.length === 0 ? <p>购物车为空</p> : (
        <ul>
          {items.map(item => (
            <li key={item.id}>
              {item.name} - ${item.price} x {item.quantity}
              <button onClick={() => updateQuantity({ id: item.id, quantity: item.quantity - 1 })}>-</button>
              <button onClick={() => updateQuantity({ id: item.id, quantity: item.quantity + 1 })}>+</button>
              <button onClick={() => removeItem(item.id)}>删除</button>
            </li>
          ))}
        </ul>
      )}
      <p>总价: ${total}</p>
    </div>
  );
}

Jotai 的优势在于:颗粒度细,只有使用特定 atom 的组件才会重新渲染。对于大型应用,可以避免不必要的渲染。同时,Jotai 不需要 Provider,但支持 Context 作用域。

方案三:Redux Toolkit

Redux Toolkit 是 Redux 官方推荐的写法,简化了样板代码。

1. 创建 Slice

创建 src/redux/store.ts


import { createSlice, configureStore, PayloadAction } from '@reduxjs/toolkit';

interface CartItem {
  id: number;
  name: string;
  price: number;
  quantity: number;
}

interface CartState {
  items: CartItem[];
}

const initialState: CartState = {
  items: [],
};

const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    addItem: (state, action: PayloadAction<Omit<CartItem, 'quantity'>>) => {
      const existing = state.items.find(i => i.id === action.payload.id);
      if (existing) {
        existing.quantity += 1;
      } else {
        state.items.push({ ...action.payload, quantity: 1 });
      }
    },
    removeItem: (state, action: PayloadAction<number>) => {
      state.items = state.items.filter(i => i.id !== action.payload);
    },
    updateQuantity: (state, action: PayloadAction<{ id: number; quantity: number }>) => {
      const item = state.items.find(i => i.id === action.payload.id);
      if (item) {
        item.quantity = Math.max(1, action.payload.quantity);
      }
    },
    clearCart: (state) => {
      state.items = [];
    },
  },
});

export const { addItem, removeItem, updateQuantity, clearCart } = cartSlice.actions;

export const store = configureStore({
  reducer: {
    cart: cartSlice.reducer,
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

2. 创建 hooks 和 Provider

创建 src/redux/hooks.ts


import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

src/main.tsx 中包裹 Provider:


import { Provider } from 'react-redux';
import { store } from './redux/store';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <Provider store={store}>
    <App />
  </Provider>
);

3. 在组件中使用

创建 src/redux/Cart.tsx


import { useAppSelector, useAppDispatch } from './hooks';
import { addItem, removeItem, updateQuantity, clearCart } from './store';

export function Cart() {
  const items = useAppSelector(state => state.cart.items);
  const dispatch = useAppDispatch();
  const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);

  return (
    <div>
      <h2>Redux 购物车</h2>
      {items.length === 0 ? <p>购物车为空</p> : (
        <ul>
          {items.map(item => (
            <li key={item.id}>
              {item.name} - ${item.price} x {item.quantity}
              <button onClick={() => dispatch(updateQuantity({ id: item.id, quantity: item.quantity - 1 }))}>-</button>
              <button onClick={() => dispatch(updateQuantity({ id: item.id, quantity: item.quantity + 1 }))}>+</button>
              <button onClick={() => dispatch(removeItem(item.id))}>删除</button>
            </li>
          ))}
        </ul>
      )}
      <p>总价: ${total}</p>
      <button onClick={() => dispatch(clearCart())}>清空购物车</button>
    </div>
  );
}

Redux 的优势在于:生态丰富,中间件(如 redux-saga、redux-thunk)成熟,适合大型团队和复杂状态逻辑。但样板代码相对较多。

性能对比

我们使用 React DevTools 的 Profiler 来测量渲染性能。在添加商品时,三种方案的表现:

  • Zustand:只有使用 store 的组件重新渲染,但若组件订阅了整个 store,即使只改变部分状态也会重渲染。建议使用 selector 精确选择。
  • Jotai:只重渲染使用了变化 atom 的组件,颗粒度最细,性能最佳。
  • Redux:通过 useSelector 可以精确选择,但 Provider 模式可能导致根组件重渲染。使用 React.memo 可优化。

> 💡 对于大多数中小型应用,性能差异可以忽略。但在大型应用中,Jotai 的原子化方案能有效减少不必要的渲染。

选型建议

| 维度 | Zustand | Jotai | Redux Toolkit | |——|———|——-|—————| | 学习曲线 | ⭐ (低) | ⭐⭐ (中) | ⭐⭐⭐ (高) | | 样板代码 | 少 | 少 | 中 | | 性能 | 好 | 优秀 | 好 | | 生态 | 一般 | 一般 | 丰富 | | 适合场景 | 中小型应用、快速原型 | 需要细粒度控制、复杂派生状态 | 大型应用、团队协作、复杂异步逻辑 |

我的建议

  • 如果你追求极简和开发效率,选 Zustand
  • 如果你需要精细化的状态管理,尤其是派生状态多,选 Jotai
  • 如果你是大型团队或需要强大的中间件支持,选 Redux Toolkit

总结

本文通过购物车案例,完整演示了 Zustand、Jotai 和 Redux Toolkit 三种状态管理方案的实现。每种方案各有优劣,选择时应根据项目规模、团队经验和具体需求来定。希望这篇教程能帮助你做出明智的选型决策。

延伸阅读

  • Zustand 官方文档:https://github.com/pmndrs/zustand
  • Jotai 官方文档:https://jotai.org/
  • Redux Toolkit 官方文档:https://redux-toolkit.js.org/