穿越组件丛林:Next.js 状态管理的三把瑞士军刀

10 阅读3分钟

作为计算机科学家,咱们得先给猴子们找个家。本文就带你把玩 ZustandJotaiRedux Toolkit 这三把瑞士军刀,看看哪一把最适合当你的“驯猴鞭”。


🔍 先给底层地基打个桩:状态到底放哪?

在 Next.js 里,React Context ≠ 状态管理器,它只是一个依赖注入器(DI 容器)。
真正需要跨组件共享的状态,要么:

  1. 抬到全局(Global Store)
  2. 原子化(Atomic Store)
  3. 事件化(Flux/Redux)
方案内存位置更新粒度触发机制适用场景
ZustandMap+useSyncExternalStore全局(可切片)发布订阅小而美、SSR 友好
JotaiWeakMap+useState原子级原子依赖追踪细粒度、可组合
Redux ToolkitcreateStore+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>
}

🎢 实战对比:一个购物车需求

特性ZustandJotaiRedux Toolkit
代码行数202545
类型支持✅(原子级推导)✅(模板啰嗦)
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!