原子化状态(Jotai / Recoil)与单一 Store(Zustand / Redux)如何选型?各自解决什么渲染痛点?
在 React 现代状态管理生态中,Zustand(代表自顶向下的单一外部 Store 模型) 与 Jotai(代表自底向上的原子化 Atomic 模型) 是最主流的两套轻量方案,它们分别契合不同的业务数据结构与更新频率特征。
一、 单一 Store(Zustand)vs 原子化状态(Jotai)核心对比
| 评估维度 | 单一 Store 模型(Zustand / Redux) | 原子化状态模型(Jotai / Recoil) |
|---|---|---|
| 状态组织方式 | 自顶向下(Top-Down):状态集中在一个外部大对象树中,组件通过 Selector 函数切片订阅 | 自底向上(Bottom-Up):状态被打散为无数独立的微小 atom,通过组合派生出复杂 derivedAtom |
| 组件外访问能力 | 极强:可在 Axios 拦截器、普通工具函数中直接调用 useStore.getState() 与 setState() |
较弱:Atom 状态默认驻留在 React Provider 组件树内(需额外持有 store 实例才能在组件外读写) |
| 动态高频点状更新性能 | 若大画布上有 5,000 个独立图元,每次更新大对象再跑 5,000 次 Selector 函数存在遍历开销 | 极佳:每个图元独立对应一个 atomFamily(id),修改单个图元仅唤醒订阅该 Atom 的那 1 个组件,复杂度 $O(1)$ |
| 代码分割(Code Splitting) | 全局 Store 通常随主包一起加载 | Atom 定义可随异步路由组件天然按需加载与销毁 |
二、 代码对照:同一派生业务在 Zustand 与 Jotai 中的实现差异
// 1. Zustand:外部单一 Store + 显式 Selector 订阅(适合全局用户态、购物车、播放器)
import { create } from 'zustand'
const useCartStore = create((set) => ({
items: [{ price: 100, qty: 2 }],
taxRate: 0.1,
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
}))
// 组件内必须显式写 Selector 精确选取所需字段,否则无关字段变化也会重渲染
function CartTotalZustand() {
const total = useCartStore((s) =>
s.items.reduce((sum, it) => sum + it.price * it.qty, 0) * (1 + s.taxRate)
)
return <span>总计:{total}</span>
}
// 2. Jotai:自底向上原子组合(适合可视化低代码画布、复杂联动电子表格、节点编辑器)
import { atom, useAtomValue } from 'jotai'
const itemsAtom = atom([{ price: 100, qty: 2 }])
const taxRateAtom = atom(0.1)
// 派生原子(Derived Atom):自动追踪依赖的原子,只有依赖项变化时才重算!
const totalAtom = atom((get) => {
const items = get(itemsAtom)
const tax = get(taxRateAtom)
return items.reduce((sum, it) => sum + it.price * it.qty, 0) * (1 + tax)
})
function CartTotalJotai() {
const total = useAtomValue(totalAtom)
return <span>总计:{total}</span>
}