React状态管理方案在2026年呈现轻量化趋势。Redux的全局store模式逐渐被Zustand和Jotai替代。Zustand采用单一store配合选择器订阅,API极简;Jotai基于原子化模型,自底向上组合状态。本文通过TypeScript实战代码对比两种方案的使用模式、性能特征和适用场景,为前端工程化选型提供参考。
Zustand快速上手与Store设计
安装Zustand:
npm install zustand
创建全局store,TypeScript类型安全定义:
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
interface UserState {
user: { id: string; name: string; role: string } | null
token: string | null
login: (user: UserState['user'], token: string) => void
logout: () => void
updateUser: (patch: Partial<NonNullable<UserState['user']>>) => void
}
export const useUserStore = create<UserState>()(
devtools(
persist(
(set, get) => ({
user: null,
token: null,
login: (user, token) => set({ user, token }),
logout: () => set({ user: null, token: null }),
updateUser: (patch) =>
set((state) => ({
user: state.user ? { ...state.user, ...patch } : null,
})),
}),
{ name: 'user-storage' }
)
)
)
组件中使用store,选择器精确订阅避免不必要重渲染:
import { useUserStore } from './stores/userStore'
function UserProfile() {
// 只订阅user字段,token变化不触发重渲染
const user = useUserStore((state) => state.user)
const updateUser = useUserStore((state) => state.updateUser)
if (!user) return <LoginGate />
return (
<div>
<h1>{user.name}</h1>
<span>角色: {user.role}</span>
<button onClick={() => updateUser({ name: '新名称' })}>
修改名称
</button>
</div>
)
}
跨组件通信无需Context Provider包裹,直接在任意组件中调用useUserStore。
Zustand异步操作与中间件
异步请求封装在store内部,配合loading状态管理:
interface ProductState {
products: Product[]
loading: boolean
error: string | null
fetchProducts: () => Promise<void>
}
export const useProductStore = create<ProductState>()(
devtools((set, get) => ({
products: [],
loading: false,
error: null,
fetchProducts: async () => {
set({ loading: true, error: null })
try {
const resp = await fetch('/api/products')
const data = await resp.json()
set({ products: data, loading: false })
} catch (err) {
set({ error: err.message, loading: false })
}
},
}))
)
使用shallow比较优化多字段订阅:
import { shallow } from 'zustand/shallow'
function ProductList() {
const { products, loading } = useProductStore(
(state) => ({ products: state.products, loading: state.loading }),
shallow
)
if (loading) return <Skeleton />
return (
<ul>
{products.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
)
}
Jotai原子化状态模型
安装Jotai:
npm install jotai
Jotai的核心概念是atom,每个atom是一个独立的状态单元:
import { atom, useAtom, useAtomValue } from 'jotai'
// 基础atom
const countAtom = atom(0)
const nameAtom = atom('')
// 派生atom(只读)
const doubleCountAtom = atom((get) => get(countAtom) * 2)
// 派生atom(可读写)
const resetCountAtom = atom(
(get) => get(countAtom),
(get, set) => set(countAtom, 0)
)
// 异步派生atom
const userDataAtom = atom(async (get) => {
const token = get(tokenAtom)
const resp = await fetch('/api/user', {
headers: { Authorization: `Bearer ${token}` }
})
return resp.json()
})
组件中使用atom:
function Counter() {
const [count, setCount] = useAtom(countAtom)
const doubleCount = useAtomValue(doubleCountAtom)
return (
<div>
<p>Count: {count}</p>
<p>Double: {doubleCount}</p>
<button onClick={() => setCount(c => c + 1)}>+1</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
)
}
Jotai原子组合与依赖追踪
Jotai的原子可以相互依赖,形成状态依赖图。当依赖的atom变化时,派生atom自动重新计算:
// 购物车原子组合
const cartAtom = atom<CartItem[]>([])
const cartTotalAtom = atom((get) => {
const cart = get(cartAtom)
return cart.reduce((sum, item) => sum + item.price * item.qty, 0)
})
const cartCountAtom = atom((get) => {
const cart = get(cartAtom)
return cart.reduce((sum, item) => sum + item.qty, 0)
})
const discountAtom = atom(0.9) // 9折
const finalPriceAtom = atom((get) => {
const total = get(cartTotalAtom)
const discount = get(discountAtom)
return Math.round(total * discount * 100) / 100
})
组件按需订阅不同的派生atom,互不影响:
function CartSummary() {
const finalPrice = useAtomValue(finalPriceAtom)
const count = useAtomValue(cartCountAtom)
return <div>共{count}件商品,合计¥{finalPrice}</div>
}
function CartTotal() {
const total = useAtomValue(cartTotalAtom)
// 只关心原始总价,不关心折扣
return <div>原价:¥{total}</div>
}
性能对比与渲染优化
Zustand和Jotai在渲染性能上有不同特征:
Zustand:选择器函数在每次store变化时执行,返回值通过Object.is比较。对象类型返回值需要配合shallow比较,否则每次都触发重渲染。
// 问题:每次都返回新对象,导致重渲染
const { a, b } = useStore((state) => ({ a: state.a, b: state.b }))
// 修复:使用shallow
const { a, b } = useStore((state) => ({ a: state.a, b: state.b }), shallow)
Jotai:atom级别的依赖追踪,只有订阅的atom变化才触发重渲染。派生atom的缓存机制避免重复计算。
// Jotai自动优化,无需手动shallow比较
const a = useAtomValue(aAtom)
const b = useAtomValue(bAtom)
// aAtom变化只重渲染订阅aAtom的组件
使用React DevTools Profiler对比两种方案在1000条列表数据更新时的渲染次数。Zustand的单一store模式下,未使用选择器的组件也会被通知(虽然不重渲染但需要执行选择器)。Jotai的原子模型天然实现了最小化订阅范围。
选型决策与迁移方案
选择Zustand的场景:
- 全局状态集中管理,如用户信息、主题配置、权限数据
- 团队熟悉Redux模式,需要平缓的学习曲线
- 需要中间件支持(persist、devtools、immer)
- 状态间关联性强,集中管理更清晰
选择Jotai的场景:
- 状态分散在不同组件中,粒度细
- 存在大量派生状态和计算属性
- 需要精细的渲染优化,避免不必要的重渲染
- 组件库设计,每个组件维护独立状态
从Redux迁移到Zustand的成本较低,store结构可以一对一映射。从Redux迁移到Jotai需要将reducer拆解为独立atom,工作量较大但长期维护性更好。
两种方案也可以共存:Zustand管理全局共享状态(用户、主题),Jotai管理业务组件内部状态(表单、表格筛选)。在响应式布局和跨端小程序开发场景中,状态管理方案的选择还需要考虑SSR兼容性和Hydration支持。Zustand通过persist中间件支持SSR,Jotai提供useHydrateAtoms处理服务端状态注水。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/react-zhuang-tai-guan-li-fang-an-dui-bi-zustand-yu-jotai/