文章

React状态管理方案对比深度解析

对比 Redux Toolkit、Zustand、Jotai 三种方案的设计理念、API 风格和适用场景,帮你在选型时做出正确决策

React状态管理方案对比深度解析

一句话概括

React 状态管理演进了一条清晰的路线:Redux(一切都规范化,适合大团队)→ Zustand(API 极简、性能 by default)→ Jotai(原子化状态,组件只订阅自己关心的那一片)。理解三者的差异就是理解「状态管理的本质矛盾:规范 vs 灵活,集中 vs 分散」。

核心知识点

1. Redux Toolkit:工业级规范,但模板代码最多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Redux Toolkit:一套标准流程
import { createSlice, configureStore } from '@reduxjs/toolkit'

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: state => { state.value += 1 },  // Immer 加持,看起来 mutable 实际 immutable
    decrement: state => { state.value -= 1 },
    addByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload
    },
  },
})

const store = configureStore({ reducer: { counter: counterSlice.reducer } })

// 组件中使用:必须 dispatch + useSelector
function Counter() {
  const count = useSelector((state: RootState) => state.counter.value)
  const dispatch = useDispatch()
  return (
    <button onClick={() => dispatch(counterSlice.actions.increment())}>
      {count}
    </button>
  )
}

适合场景:大型团队、多人协作、需要严格的 action 可追溯性、有中间件需求(Redux-Saga/Thunk/Logger)。代价是——每个状态字段都要经过 slice → reducer → selector 的三层包装。

2. Zustand:API 最简,性能最优

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { create } from 'zustand'

interface CounterStore {
  count: number
  increment: () => void
  decrement: () => void
}

const useCounterStore = create<CounterStore>(set => ({
  count: 0,
  increment: () => set(state => ({ count: state.count + 1 })),
  decrement: () => set(state => ({ count: state.count - 1 })),
}))

// 组件中使用:直接 hook,不需要 Provider!
function Counter() {
  const count = useCounterStore(s => s.count)  // selector 精准订阅
  const increment = useCounterStore(s => s.increment)
  return <button onClick={increment}>{count}</button>
}

和最棒的特性——不需要 Provider 包裹:Zustand 的 store 就是一个独立模块,任何组件 import { useStore } 后立即可用。带来的工程收益:没有 Provider 嵌套地狱、store 可以在 React 之外使用(比如在 axios 拦截器里改状态)。

精准渲染:useCounterStore(s => s.count) 只订阅 count 字段,其他字段变化时不会触发这个组件重渲染。这是 Zustand 比 Context 方案性能更好的根本原因。

3. Jotai:原子化状态,React 原生味道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { atom, useAtom } from 'jotai'

// 状态的最小单位是 atom,不是 store
const countAtom = atom(0)
const userAtom = atom<User | null>(null)

// 派生 atom:自动依赖追踪
const greetingAtom = atom(get => {
  const user = get(userAtom)
  const count = get(countAtom)
  return user ? `${user.name}: ${count}` : `Guest: ${count}`
})

function Counter() {
  const [count, setCount] = useAtom(countAtom)  // 像 useState 一样用
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

function Greeting() {
  const [greeting] = useAtom(greetingAtom)  // 只订阅派生值
  return <div>{greeting}</div>
}

Jotai 的哲学:状态像乐高积木——小 atom 组合成大状态。每个组件只订阅自己用的 atom,天然避免不必要的渲染。API 和 useState 一致,心智成本几乎为零。

4. 三者在同一场景下的对比

假如同样的功能——管理用户信息和购物车:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ──── Redux Toolkit ────
// 文件: store/userSlice.ts, store/cartSlice.ts, store/index.ts
// 需要: createSlice × 2, configureStore, Provider, useSelector, useDispatch
// 约 60 行

// ──── Zustand ────
// 文件: stores/user.ts, stores/cart.ts(或合在一起)
const useUserStore = create<UserStore>(set => ({
  user: null,
  setUser: user => set({ user }),
}))
const useCartStore = create<CartStore>(set => ({
  items: [],
  addItem: item => set(s => ({ items: [...s.items, item] })),
}))
// 约 20 行,不需要 Provider

// ──── Jotai ────
const userAtom = atom<User | null>(null)
const cartItemsAtom = atom<Item[]>([])
const cartTotalAtom = atom(get => get(cartItemsAtom).reduce((sum, i) => sum + i.price, 0))
// 约 15 行,像 useState 一样直觉

5. 选型决策

场景推荐方案原因
大型团队、多人协作Redux Toolkitaction 可追溯、中间件生态
中小型项目、追求开发效率Zustand零模板代码、无 Provider
细粒度状态、复杂派生Jotai原子化天然隔离渲染
已用 Redux 的老项目不动或渐进迁移到 RTK迁移成本高于收益
SSR 应用Zustand 或 JotaiProvider 地狱在 SSR 下更严重

其实你每天都在用

  1. Redux DevTools 的时间旅行:点击任意 action 回退到历史快照——这是 Redux 架构带来的独有能力,Zustand 和 Jotai 做不到这样完整的 action 回放。
  2. Zustand 的 persist 中间件:create(persist(store, { name: 'my-store' }))——一行代码把状态持久化到 localStorage,这是 Zustand 中间件的威力。
  3. Jotai 的 atomWithStorage:和 Zustand 的 persist 类似,一行代码搞定 localStorage 同步。
  4. Context 的渲染地狱:当你用 Context 做状态管理时,Provider value 一变所有消费者都重新渲染——这就是为什么需要 Zustand/Jotai 这种 selector-based 的方案。
  5. Immer 的魔法:Redux Toolkit 的 state.value += 1 看起来是 mutable 操作,实际上 Immer 帮你变成了 immutable——这是写起来最舒服的不可变更新。

常见误解(FAQ)

❌ 误区:「Redux 已死,Zustand/Jotai 才是未来」

Redux 在需要严格规范的大型团队项目中不可替代。action type 的全局唯一性、中间件的洋葱模型、DevTools 的时间旅行——这些是 Zustand/Jotai 刻意放弃的能力。不是 Redux 被淘汰,而是「不是每个项目都需要 Redux 级别的规范」。

❌ 误区:「Zustand 不需要 Provider,所以它是全局变量」

Zustand 确实是非 React 依赖的纯 JS 模块,但它的 set 方法内部走的是 React 的 useSyncExternalStore——合法地融入了 React 18 的并发渲染体系。它既能脱离 React 使用,又在 React 环境里正确参与调度。

❌ 误区:「Jotai 不就是 Context + useState 吗」

表面上像,但本质不同。Context 的消费者会订阅 Provider 的全部 value,而 Jotai 的 atom 订阅是字段级别的——useAtom(countAtom) 只会在 countAtom 变化时重渲染,其他 atom 怎么变都无关。这是渲染粒度上的本质差异。

❌ 误区:「状态管理库选一个就够了,不需要组合使用」

实际上最佳实践往往是组合使用:服务端缓存(TanStack Query/SWR)管理 API 数据,Zustand 管理 UI 交互状态,Jotai 管理表单和原子状态。不同性质的状态用不同工具——「一刀切」反而造成复杂度。

一句话总结

Redux 是为「你可能会搞砸状态流」设计的——每一步都留痕;Zustand 是为「你只是想存个值」设计的——两行代码搞定;Jotai 是为「每个状态都是独立的一部分」设计的——像搭乐高一样搭状态。

本文由作者按照 CC BY 4.0 进行授权