React状态管理方案对比深度解析
对比 Redux Toolkit、Zustand、Jotai 三种方案的设计理念、API 风格和适用场景,帮你在选型时做出正确决策
一句话概括
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 Toolkit | action 可追溯、中间件生态 |
| 中小型项目、追求开发效率 | Zustand | 零模板代码、无 Provider |
| 细粒度状态、复杂派生 | Jotai | 原子化天然隔离渲染 |
| 已用 Redux 的老项目 | 不动或渐进迁移到 RTK | 迁移成本高于收益 |
| SSR 应用 | Zustand 或 Jotai | Provider 地狱在 SSR 下更严重 |
其实你每天都在用
- Redux DevTools 的时间旅行:点击任意 action 回退到历史快照——这是 Redux 架构带来的独有能力,Zustand 和 Jotai 做不到这样完整的 action 回放。
- Zustand 的
persist中间件:create(persist(store, { name: 'my-store' }))——一行代码把状态持久化到 localStorage,这是 Zustand 中间件的威力。 - Jotai 的
atomWithStorage:和 Zustand 的 persist 类似,一行代码搞定 localStorage 同步。 - Context 的渲染地狱:当你用 Context 做状态管理时,Provider value 一变所有消费者都重新渲染——这就是为什么需要 Zustand/Jotai 这种 selector-based 的方案。
- 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 是为「每个状态都是独立的一部分」设计的——像搭乐高一样搭状态。