Redux 太重了,用 Zustand 几行代码把状态管起来
React项目里状态管理一直是个绕不开的话题。Redux太重,Context嵌套多了性能拉胯,MobX上手有门槛。Zustand这个小库用起来就一个字,爽。API极少,没有模板代码,TypeScript支持也好,几行代码就能跑起来。
装包和建Store
npm install zustand
定义一个购物车store:
import { create } from 'zustand'
interface CartItem {
id: number
name: string
price: number
qty: number
}
interface CartStore {
items: CartItem[]
addItem: (item: Omit<CartItem, 'qty'>) => void
removeItem: (id: number) => void
updateQty: (id: number, qty: number) => void
clear: () => void
total: () => number
}
const useCartStore = create<CartStore>((set, get) => ({
items: [],
addItem: (item) => set((state) => {
const existing = state.items.find(i => i.id === item.id)
if (existing) {
return {
items: state.items.map(i =>
i.id === item.id ? { ...i, qty: i.qty + 1 } : i
)
}
}
return { items: [...state.items, { ...item, qty: 1 }] }
}),
removeItem: (id) => set((state) => ({
items: state.items.filter(i => i.id !== id)
})),
updateQty: (id, qty) => set((state) => ({
items: state.items.map(i =>
i.id === id ? { ...i, qty: Math.max(0, qty) } : i
)
})),
clear: () => set({ items: [] }),
total: () => get().items.reduce((sum, i) => sum + i.price * i.qty, 0),
}))
create函数接收一个初始化函数,参数是set和get。set改状态,get读当前状态。没有reducer、没有action type、没有dispatch,直接调函数就改了。
组件里用
function ProductList() {
const addItem = useCartStore(s => s.addItem)
const products = [
{ id: 1, name: '键盘', price: 299 },
{ id: 2, name: '鼠标', price: 99 },
]
return (
<div>
{products.map(p => (
<div key={p.id}>
<span>{p.name} - ¥{p.price}</span>
<button onClick={() => addItem(p)}>加入购物车</button>
</div>
))}
</div>
)
}
function Cart() {
const items = useCartStore(s => s.items)
const total = useCartStore(s => s.total)
const updateQty = useCartStore(s => s.updateQty)
if (items.length === 0) return <p>购物车空空如也</p>
return (
<div>
{items.map(item => (
<div key={item.id}>
<span>{item.name} × {item.qty}</span>
<button onClick={() => updateQty(item.id, item.qty + 1)}>+</button>
<button onClick={() => updateQty(item.id, item.qty - 1)}>-</button>
</div>
))}
<p>合计:¥{total()}</p>
</div>
)
}
注意组件里用useCartStore(s => s.items)选择器写法,只订阅items的变化。如果写useCartStore()拿整个store,任何状态变了组件都会重渲染,性能就白瞎了。
持久化存储
刷新页面状态丢了?加个persist中间件:
import { persist } from 'zustand/middleware'
const useCartStore = create<CartStore>()(
persist(
(set, get) => ({
// ...同上
}),
{ name: 'cart-storage' }
)
)
自动存到localStorage,key叫cart-storage。刷新页面状态还在,关浏览器也在。想存sessionStorage改个storage参数就行。
Zustand vs Redux vs Context对比
| 特性 | Zustand | Redux Toolkit | Context API |
|---|---|---|---|
| 包体积 | ~1KB | ~15KB | 0(内置) |
| 模板代码 | 几乎没有 | 较多 | 中等 |
| TypeScript支持 | 优秀 | 优秀 | 一般 |
| 性能 | 选择器精准更新 | 需要selector | 全量重渲染 |
| 中间件 | persist/devtools | 丰富 | 无 |
| 学习成本 | 很低 | 中等 | 低 |
| 适合场景 | 中小型应用 | 大型复杂应用 | 简单共享状态 |
小到中型项目选Zustand够用了,大型项目多人协作Redux的约束力还是有价值的。Context适合那种一次设置全局只读的场景,比如主题、语言。
异步action处理
Zustand里处理异步不用thunk、不用saga,直接写async函数:
interface UserStore {
user: User | null
loading: boolean
fetchUser: (id: number) => Promise<void>
}
const useUserStore = create<UserStore>((set) => ({
user: null,
loading: false,
fetchUser: async (id) => {
set({ loading: true })
try {
const res = await fetch(`/api/users/${id}`)
const user = await res.json()
set({ user, loading: false })
} catch {
set({ loading: false })
}
},
}))
组件里useUserStore(s => s.fetchUser)拿到函数,useEffect里调一下就完事。
跨组件通信
多个组件共享一个store天然支持,Zustand是全局单例。但有时候你想让某个组件局部使用store,可以用createStore创建独立实例:
import { createStore, useStore } from 'zustand'
const counterStore = createStore(() => ({ count: 0 }))
function Counter() {
const count = useStore(counterStore, s => s.count)
return <button onClick={() => counterStore.setState(s => ({ count: s.count + 1 }))}>
{count}
</button>
}
这样每个组件实例可以有自己的store,互不干扰。
Zustand的哲学就是少即是多。没有reducer、没有action creator、没有dispatch,一个create函数搞定状态定义和修改。TypeScript类型推导也顺滑,基本不用手写类型注解。项目里用了一个月,最大的感受是心智负担小,写状态管理的代码跟写普通函数没太大区别。
标签: React,TypeScript,Zustand,前端,状态管理
总结
Zustand 把状态管理从「写一堆模板代码」变回「定义一个函数」:create 建 store,组件里当 hook 用,要落盘加个 persist 中间件就够。它的定位很明确——中小型项目的状态容器,真遇到复杂数据流和中间件需求,Redux 那套还是有它的价值。挑之前先想清楚状态有多复杂,别为了轻而轻。
- 点赞
- 收藏
- 关注作者
评论(0)