作为计算机科学家,咱们得先给猴子们找个家。本文就带你把玩 Zustand、Jotai、Redux Toolkit 这三把瑞士军刀,看看哪一把最适合当你的“驯猴鞭”。
🔍 先给底层地基打个桩:状态到底放哪?
在 Next.js 里,React Context ≠ 状态管理器,它只是一个依赖注入器(DI 容器)。
真正需要跨组件共享的状态,要么:
- 抬到全局(Global Store)
- 原子化(Atomic Store)
- 事件化(Flux/Redux)
方案 | 内存位置 | 更新粒度 | 触发机制 | 适用场景 |
---|---|---|---|---|
Zustand | Map +useSyncExternalStore | 全局(可切片) | 发布订阅 | 小而美、SSR 友好 |
Jotai | WeakMap +useState | 原子级 | 原子依赖追踪 | 细粒度、可组合 |
Redux Toolkit | createStore +Context | 全局不可变树 | 纯函数 reducer | 大工程、时间旅行、DevTools |
🐻 Zustand:一只极简棕熊
“熊出没,请注意——它不需要 Provider!”
安装
npm i zustand
创建 Store(底层原理:useSyncExternalStore
+ Object.is
浅比较)
// store/useBearStore.js
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
export const useBearStore = create(
devtools(
persist(
(set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}),
{ name: 'bear-storage' } // 持久化到 localStorage
)
)
)
在组件里直接掏蜂蜜
// components/BearCounter.js
import { useBearStore } from '../store/useBearStore'
export default function BearCounter() {
const bears = useBearStore((state) => state.bears)
return <h1>{bears} 🐻 around here</h1>
}
// controls/Controls.js
import { useBearStore } from '../store/useBearStore'
export default function Controls() {
const increasePopulation = useBearStore((state) => state.increasePopulation)
return <button onClick={increasePopulation}>one up</button>
}
注意:Zustand 在 SSR 时会把
create
的 store 挂在 module scope,因此每个请求共享同一份内存。如果需要隔离,请用create(() => …)
的工厂模式。
⚛️ Jotai:乐高积木式原子
“原子太小,但拼起来就能造火箭。”
安装
npm i jotai
定义原子(底层:WeakMap
+ useState
的链表)
// atoms/index.js
import { atom } from 'jotai'
export const bearsAtom = atom(0)
export const doubledBearsAtom = atom(
(get) => get(bearsAtom) * 2,
(get, set, newValue) => set(bearsAtom, newValue / 2)
)
在组件里搭积木
// components/BearCounter.js
import { useAtom } from 'jotai'
import { bearsAtom } from '../atoms'
export default function BearCounter() {
const [bears] = useAtom(bearsAtom)
return <h1>{bears} 🧩 atoms</h1>
}
// controls/Controls.js
import { useAtom } from 'jotai'
import { bearsAtom } from '../atoms'
export default function Controls() {
const [bears, setBears] = useAtom(bearsAtom)
return (
<button onClick={() => setBears((c) => c + 1)}>
add atom
</button>
)
}
进阶:异步原子
export const bearsAsyncAtom = atom(async () => {
const res = await fetch('/api/bears')
return res.json()
})
异步原子会触发 Suspense,Next.js 13+ 的
app/
目录下需配合<Suspense>
包裹。
🦾 Redux Toolkit:时间旅行装甲车
“Redux 就像一辆装甲车——慢,但能扛核弹。”
安装
npm i @reduxjs/toolkit react-redux
创建 Slice(底层:immer
+ serializableCheck
)
// store/bearsSlice.js
import { createSlice } from '@reduxjs/toolkit'
const bearsSlice = createSlice({
name: 'bears',
initialState: { count: 0 },
reducers: {
incremented: (state) => {
state.count += 1 // 直接改,immer 帮你做不可变
},
reset: (state) => {
state.count = 0
},
},
})
export const { incremented, reset } = bearsSlice.actions
export default bearsSlice.reducer
配置 Store
// store/index.js
import { configureStore } from '@reduxjs/toolkit'
import bearsReducer from './bearsSlice'
export const store = configureStore({
reducer: {
bears: bearsReducer,
},
})
在 _app.js
注入 Provider
// pages/_app.js
import { Provider } from 'react-redux'
import { store } from '../store'
export default function MyApp({ Component, pageProps }) {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
组件使用
// components/BearCounter.js
import { useSelector } from 'react-redux'
export default function BearCounter() {
const count = useSelector((state) => state.bears.count)
return <h1>{count} 🦾 bears in the tank</h1>
}
// controls/Controls.js
import { useDispatch } from 'react-redux'
import { incremented } from '../store/bearsSlice'
export default function Controls() {
const dispatch = useDispatch()
return <button onClick={() => dispatch(incremented())}>dispatch!</button>
}
🎢 实战对比:一个购物车需求
特性 | Zustand | Jotai | Redux Toolkit |
---|---|---|---|
代码行数 | 20 | 25 | 45 |
类型支持 | ✅ | ✅(原子级推导) | ✅(模板啰嗦) |
SSR 隔离 | ⚠️ 需工厂函数 | ✅ 原子天然隔离 | ✅ Provider 隔离 |
DevTools | ✅(需插件) | ✅(需插件) | ✅(内置) |
时间旅行 | ❌ | ❌ | ✅ |
学习曲线 | 🐰 兔子 | 🐈 猫咪 | 🐘 大象 |
🖼️ 配图建议(用 ASCII 凑合)
Zustand: 一只熊坐在 Map 上
┌────────────┐
│ Map() │
│ ┌────────┐ │
│ │ bears │ │
│ │ 42 │ │
│ └────────┘ │
└────────────┘
Jotai: 乐高原子链
⚛️→⚛️→⚛️→⚛️
Redux: 装甲车
[Store]──▶[Reducer]──▶[Middleware]──▶[View]
🏁 总结:给猴子选房子
- 小项目 / 原型:Zustand(轻,快,无 Provider)
- 组合爆炸的细粒度:Jotai(原子乐高,可推导)
- 团队协作 + DevTools:Redux Toolkit(时间旅行、插件生态)
“没有银弹,只有适合场景的香蕉。”
愿你在 Next.js 的雨林里,不再被猴子抢走午餐。
Happy hacking!