Zustand
下面,我们来系统的梳理关于 Zustand 状态管理 的基本知识点:
一、Zustand 概述
1.1 什么是 Zustand?
Zustand(德语中意为“状态”)是一个轻量级、快速且可扩展的 React 状态管理库。它由 React 社区的知名开发者(React Spring 的创建者)开发,旨在提供简单高效的全局状态管理解决方案。
1.2 为什么选择 Zustand?
- 极简 API:只需几行代码即可创建 store
- 无样板代码:不需要 Provider 包裹组件
- 高性能:精确的组件更新,避免不必要的重渲染
- 轻量级:核心库仅约 1kB(gzipped)
- 灵活性强:支持中间件、TypeScript 和 React 并发模式
- 开发体验好:内置 DevTools 支持
1.3 核心特点
- 基于 Hook 的状态管理
- 不可变状态更新
- 支持异步操作
- 中间件系统
- 零依赖
二、核心概念
2.1 Store
Store 是 Zustand 的核心概念,是一个包含状态和操作状态的方法的对象。
import create from 'zustand'const useStore = create((set) => ({count: 0,increment: () => set((state) => ({ count: state.count + 1 })),decrement: () => set((state) => ({ count: state.count - 1 })),
}))
2.2 Set 函数
用于更新状态的函数,类似于 React 的 setState:
set(state => ({ ...state, count: state.count + 1 }))
2.3 Get 函数
在 store 内部获取当前状态:
const useStore = create((set, get) => ({count: 0,logCount: () => {console.log(`Current count: ${get().count}`)}
}))
三、基本用法
3.1 安装
npm install zustand
# 或
yarn add zustand
3.2 创建 Store
import create from 'zustand'const useCounterStore = create((set) => ({count: 0,increment: () => set((state) => ({ count: state.count + 1 })),decrement: () => set((state) => ({ count: state.count - 1 })),reset: () => set({ count: 0 }),
}))
3.3 在组件中使用
function Counter() {const { count, increment, decrement } = useCounterStore()return (<div><h1>{count}</h1><button onClick={increment}>+</button><button onClick={decrement}>-</button></div>)
}
3.4 选择部分状态
避免不必要的重渲染:
function IncrementButton() {const increment = useCounterStore(state => state.increment)return <button onClick={increment}>+</button>
}
四、高级特性
4.1 中间件
Zustand 提供强大的中间件系统:
4.1.1 持久化中间件
import create from 'zustand'
import { persist } from 'zustand/middleware'const useAuthStore = create(persist((set) => ({user: null,login: (user) => set({ user }),logout: () => set({ user: null }),}),{name: 'auth-storage', // 存储键名getStorage: () => localStorage, // 存储引擎}
))
4.1.2 Immer 中间件
简化不可变更新:
import create from 'zustand'
import { immer } from 'zustand/middleware/immer'const useUserStore = create(immer((set) => ({user: {name: 'John',address: {city: 'New York',zip: '10001'}},updateCity: (city) => set(state => {state.user.address.city = city})
})))
4.1.3 Redux DevTools 集成
import create from 'zustand'
import { devtools } from 'zustand/middleware'const useStore = create(devtools((set) => ({count: 0,increment: () => set(state => ({ count: state.count + 1 }))
})))
4.2 异步操作
const useProductsStore = create((set) => ({products: [],loading: false,error: null,fetchProducts: async () => {set({ loading: true, error: null })try {const response = await fetch('/api/products')const products = await response.json()set({ products, loading: false })} catch (error) {set({ error: error.message, loading: false })}}
}))
4.3 计算属性
const useCartStore = create((set, get) => ({items: [],addItem: (item) => set(state => ({ items: [...state.items, item] })),removeItem: (id) => set(state => ({ items: state.items.filter(item => item.id !== id)})),get totalPrice() {return get().items.reduce((sum, item) => sum + item.price, 0)}
}))
4.4 组合 Store
// store/counter.js
export const useCounterStore = create((set) => ({count: 0,increment: () => set(state => ({ count: state.count + 1 })),
}))// store/user.js
export const useUserStore = create((set) => ({user: null,login: (user) => set({ user }),
}))// 在组件中使用
function CombinedComponent() {const count = useCounterStore(state => state.count)const user = useUserStore(state => state.user)return <div>{user.name}: {count}</div>
}
五、性能优化
5.1 精确状态选择
// 只订阅需要的状态
function UserProfile() {const user = useUserStore(state => state.user)// ...
}
5.2 使用 shallow 比较
当需要选择多个状态时:
import shallow from 'zustand/shallow'function CartSummary() {const { items, totalPrice } = useCartStore(state => ({ items: state.items, totalPrice: state.totalPrice }),shallow // 浅比较)// ...
}
5.3 批量更新
const useStore = create((set) => ({firstName: 'John',lastName: 'Doe',setFullName: (firstName, lastName) => set({ firstName, lastName })
}))
5.4 使用选择器函数
// store/selectors.js
export const selectCompletedItems = (state) => state.items.filter(item => item.completed)// 组件中使用
function CompletedItems() {const completedItems = useCartStore(selectCompletedItems)return (<ul>{completedItems.map(item => (<li key={item.id}>{item.name}</li>))}</ul>)
}
六、实践
6.1 项目结构
src/├── stores/│ ├── useAuthStore.js│ ├── useCartStore.js│ ├── useProductStore.js│ └── index.js├── components/├── hooks/└── App.js
6.2 类型安全(TypeScript)
interface CounterState {count: numberincrement: () => voiddecrement: () => void
}const useCounterStore = create<CounterState>((set) => ({count: 0,increment: () => set(state => ({ count: state.count + 1 })),decrement: () => set(state => ({ count: state.count - 1 })),
}))
6.3 测试策略
// __tests__/counterStore.test.js
import create from 'zustand'
import { act } from '@testing-library/react'test('counter store', () => {const useStore = create((set) => ({count: 0,increment: () => set(state => ({ count: state.count + 1 })),}))let stateuseStore.subscribe(s => (state = s))act(() => {useStore.getState().increment()})expect(state.count).toBe(1)
})
6.4 状态设计原则
- 单一职责:每个 store 关注单一领域
- 最小化状态:只存储必要数据
- 派生数据:使用 getter 或选择器
- 操作集中:将业务逻辑放在 store 中
七、与其他状态库对比
特性 | Zustand | Redux | Recoil | Context |
---|---|---|---|---|
包大小 | ~1kB | ~5kB | ~14kB | React 内置 |
学习曲线 | 简单 | 陡峭 | 中等 | 简单 |
性能 | 高 | 中 | 高 | 低 |
DevTools | 支持 | 优秀 | 支持 | 无 |
中间件 | 支持 | 支持 | 无 | 无 |
异步支持 | 原生 | 需中间件 | 原生 | 原生 |
TypeScript | 优秀 | 优秀 | 优秀 | 良好 |
八、案例:电商应用
8.1 认证 Store
// stores/useAuthStore.js
import create from 'zustand'
import { persist } from 'zustand/middleware'export const useAuthStore = create(persist((set) => ({user: null,token: null,isAuthenticated: false,login: async (credentials) => {const response = await fetch('/api/login', {method: 'POST',body: JSON.stringify(credentials)})const { user, token } = await response.json()set({ user, token, isAuthenticated: true })},logout: () => {set({ user: null, token: null, isAuthenticated: false })}}),{name: 'auth-store',getStorage: () => localStorage,}
))
8.2 购物车 Store
// stores/useCartStore.js
import create from 'zustand'
import { immer } from 'zustand/middleware/immer'export const useCartStore = create(immer((set, get) => ({items: [],loading: false,error: null,addItem: (product) => {const existingItem = get().items.find(item => item.id === product.id)if (existingItem) {set(state => {const item = state.items.find(item => item.id === product.id)item.quantity += 1})} else {set(state => { state.items.push({ ...product, quantity: 1 }) })}},removeItem: (id) => {set(state => {state.items = state.items.filter(item => item.id !== id)})},updateQuantity: (id, quantity) => {if (quantity < 1) {get().removeItem(id)return}set(state => {const item = state.items.find(item => item.id === id)if (item) item.quantity = quantity})},get totalItems() {return get().items.reduce((sum, item) => sum + item.quantity, 0)},get totalPrice() {return get().items.reduce((sum, item) => sum + item.price * item.quantity, 0)},clearCart: () => set({ items: [] }),
}))
8.3 产品列表 Store
// stores/useProductStore.js
import create from 'zustand'export const useProductStore = create((set) => ({products: [],featuredProducts: [],loading: false,error: null,filters: {category: 'all',sort: 'price-asc',search: '',},fetchProducts: async () => {set({ loading: true, error: null })try {const response = await fetch('/api/products')const products = await response.json()set({ products, loading: false })} catch (error) {set({ error: 'Failed to load products', loading: false })}},fetchFeatured: async () => {set({ loading: true })try {const response = await fetch('/api/products/featured')const featured = await response.json()set({ featuredProducts: featured, loading: false })} catch {set({ loading: false })}},setFilter: (filter, value) => {set(state => ({filters: { ...state.filters, [filter]: value }}))},get filteredProducts() {return (state) => {const { products, filters } = statelet result = [...products]if (filters.category !== 'all') {result = result.filter(p => p.category === filters.category)}if (filters.search) {const searchTerm = filters.search.toLowerCase()result = result.filter(p => p.name.toLowerCase().includes(searchTerm) || p.description.toLowerCase().includes(searchTerm)}if (filters.sort === 'price-asc') {result.sort((a, b) => a.price - b.price)} else if (filters.sort === 'price-desc') {result.sort((a, b) => b.price - a.price)}return result}}
}))
8.4 组件集成示例
// components/ProductList.jsx
import { useProductStore } from '../stores/useProductStore'
import { useCartStore } from '../stores/useCartStore'function ProductList() {const { products, loading, error, setFilter } = useProductStore()const addToCart = useCartStore(state => state.addItem)if (loading) return <div>Loading products...</div>if (error) return <div>Error: {error}</div>return (<div><div className="filters"><select onChange={(e) => setFilter('category', e.target.value)}><option value="all">All Categories</option><option value="electronics">Electronics</option><option value="clothing">Clothing</option></select></div><div className="product-grid">{products.map(product => (<div key={product.id} className="product-card"><h3>{product.name}</h3><p>${product.price}</p><button onClick={() => addToCart(product)}>Add to Cart</button></div>))}</div></div>)
}// components/CartIcon.jsx
import { useCartStore } from '../stores/useCartStore'function CartIcon() {const totalItems = useCartStore(state => state.totalItems)return (<div className="cart-icon"><span>🛒</span>{totalItems > 0 && <span className="badge">{totalItems}</span>}</div>)
}
九、总结
9.1 Zustand 核心优势
- 极简 API:学习成本低,易于上手
- 高性能:精确的状态订阅机制
- 灵活扩展:强大的中间件系统
- 无 Provider 负担:简化应用结构
- TypeScript 友好:完整的类型支持
9.2 实践总结
- 单一职责 Store:每个 store 关注单一领域
- 精确状态选择:避免不必要的重渲染
- 合理使用中间件:持久化、Immer 等
- 类型安全:使用 TypeScript 增强可靠性
- 模块化组织:按功能拆分 store
- 测试驱动:编写 store 单元测试
9.3 适用场景
- 中小型应用的状态管理
- 需要轻量级解决方案
- 希望避免复杂样板代码
- 需要快速开发迭代
- 对性能要求较高的应用