Appearance
Zustand
本文介绍轻量级状态管理库Zustand,包括其核心特性、基本用法、中间件支持以及与React组件的集成方式。
简介
Zustand是一个轻量级的状态管理库,专为React应用设计,但也可以在非React环境中使用。它采用简单直观的API,解决了Redux等传统状态管理库的复杂性问题,同时保留了强大的功能和灵活性。
核心特性
- 简单的API:没有样板代码,没有Provider,没有高阶组件
- 轻量级:体积小(约3KB),性能高
- 不依赖Context:避免了Context导致的不必要重渲染
- 支持中间件:内置多种中间件,如持久化、日志记录等
- TypeScript友好:完整的类型支持
- 支持React之外的环境:可以在任何JavaScript环境中使用
安装
bash
# 使用npm
npm install zustand
# 使用yarn
yarn add zustand
# 使用pnpm
pnpm add zustand基本用法
创建Store
使用create函数创建一个store:
jsx
import { create } from 'zustand'
// 定义store
const useStore = create((set) => ({
// 状态
bears: 0,
// 更新状态的方法
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}))在组件中使用
jsx
import React from 'react'
import { useStore } from './store' // 假设上面的store代码保存在store.js文件中
function BearCounter() {
// 从store中提取状态
const bears = useStore((state) => state.bears)
return <h1>{bears} bears around here...</h1>
}
function Controls() {
// 从store中提取方法
const increasePopulation = useStore((state) => state.increasePopulation)
const removeAllBears = useStore((state) => state.removeAllBears)
return (
<div>
<button onClick={increasePopulation}>增加熊的数量</button>
<button onClick={removeAllBears}>移除所有熊</button>
</div>
)
}
function App() {
return (
<>
<BearCounter />
<Controls />
</>
)
}异步操作
Zustand可以轻松处理异步操作:
jsx
import { create } from 'zustand'
const useStore = create((set) => ({
fishes: 0,
loading: false,
error: null,
// 异步增加鱼的数量
fetchFishes: async () => {
try {
set({ loading: true, error: null })
// 模拟API调用
const response = await fetch('https://api.example.com/fishes')
const data = await response.json()
set({ fishes: data.count, loading: false })
} catch (error) {
set({ error: error.message, loading: false })
}
},
}))使用TypeScript
Zustand对TypeScript有很好的支持:
typescript
import { create } from 'zustand'
// 定义状态类型
interface BearState {
bears: number
increasePopulation: () => void
removeAllBears: () => void
}
// 创建有类型的store
const useStore = create<BearState>((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}))高级用法
状态选择器
使用选择器可以优化性能,只在相关状态变化时重新渲染组件:
jsx
function BearCounter() {
// 只有bears变化时才会重新渲染
const bears = useStore((state) => state.bears)
return <h1>{bears} bears around here...</h1>
}状态更新
Zustand提供了多种更新状态的方式:
jsx
const useStore = create((set) => ({
// 状态
count: 0,
text: 'hello',
// 1. 直接替换状态
setCount: (newCount) => set({ count: newCount }),
// 2. 基于当前状态更新
increment: () => set((state) => ({ count: state.count + 1 })),
// 3. 部分更新状态(保留其他字段)
updateText: (newText) => set({ text: newText }),
// 4. 完全替换状态(不保留其他字段)
reset: () => set({ count: 0, text: '' }, true),
}))读取状态(不在组件中)
可以在组件外部读取状态:
jsx
const { getState, setState } = useStore
// 读取当前状态
const bears = getState().bears
// 更新状态
setState({ bears: bears + 1 })多个Store
可以创建多个独立的store:
jsx
import { create } from 'zustand'
// 用户store
const useUserStore = create((set) => ({
user: null,
loading: false,
login: async (credentials) => {
set({ loading: true })
// 登录逻辑
// ...
},
logout: () => set({ user: null }),
}))
// 购物车store
const useCartStore = create((set) => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
removeItem: (id) => set((state) => ({
items: state.items.filter(item => item.id !== id)
})),
clearCart: () => set({ items: [] }),
}))中间件
Zustand支持中间件,可以扩展store的功能。
持久化中间件
使用persist中间件将状态保存到localStorage或其他存储中:
jsx
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
const useStore = create(
persist(
(set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}),
{
name: 'bear-storage', // 存储的唯一名称
storage: createJSONStorage(() => localStorage), // 使用localStorage
// 可选:只持久化某些状态
partialize: (state) => ({ bears: state.bears }),
}
)
)日志中间件
使用devtools中间件可以在Redux DevTools中查看状态变化:
jsx
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
const useStore = create(
devtools(
(set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
})
)
)组合中间件
可以组合多个中间件:
jsx
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
const useStore = create(
devtools(
persist(
(set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}),
{
name: 'bear-storage',
}
)
)
)自定义中间件
可以创建自定义中间件:
jsx
const logger = (config) => (set, get, api) =>
config(
(...args) => {
console.log('applying', args)
set(...args)
console.log('new state', get())
},
get,
api
)
const useStore = create(
logger((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
}))
)与React集成
使用Context优化
对于大型应用,可以使用Context来避免prop drilling:
jsx
import { create } from 'zustand'
import { createContext, useContext } from 'react'
// 创建context
const StoreContext = createContext()
// 创建store hook
const createStore = () =>
create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}))
// 提供store的Provider组件
export const StoreProvider = ({ children }) => {
const storeRef = useRef()
if (!storeRef.current) {
storeRef.current = createStore()
}
return (
<StoreContext.Provider value={storeRef.current}>
{children}
</StoreContext.Provider>
)
}
// 自定义hook使用store
export const useStore = (selector) => {
const store = useContext(StoreContext)
return store(selector)
}与React Query结合
Zustand可以与React Query等数据获取库结合使用:
jsx
import { create } from 'zustand'
import { useQuery } from 'react-query'
// 创建store
const useStore = create((set) => ({
selectedUserId: null,
setSelectedUserId: (id) => set({ selectedUserId: id }),
}))
// 在组件中使用
function UserDetails() {
const selectedUserId = useStore((state) => state.selectedUserId)
// 使用React Query获取数据
const { data, isLoading, error } = useQuery(
['user', selectedUserId],
() => fetch(`/api/users/${selectedUserId}`).then(res => res.json()),
{ enabled: !!selectedUserId }
)
if (!selectedUserId) return <p>请选择一个用户</p>
if (isLoading) return <p>加载中...</p>
if (error) return <p>错误: {error.message}</p>
return (
<div>
<h2>{data.name}</h2>
<p>{data.email}</p>
</div>
)
}性能优化
避免不必要的重渲染
使用选择器可以避免不必要的重渲染:
jsx
// 不好的做法:获取整个状态对象
const state = useStore()
const { bears, fishes } = state // 任何状态变化都会导致重渲染
// 好的做法:只选择需要的状态
const bears = useStore((state) => state.bears) // 只有bears变化时才重渲染
const fishes = useStore((state) => state.fishes) // 只有fishes变化时才重渲染使用shallow比较
对于对象或数组状态,使用shallow比较可以避免不必要的重渲染:
jsx
import { create } from 'zustand'
import { shallow } from 'zustand/shallow'
function Component() {
// 使用shallow比较,只有当数组中的元素实际变化时才重渲染
const [bears, fishes] = useStore(
(state) => [state.bears, state.fishes],
shallow
)
// 对于对象也可以使用shallow
const { bears, fishes } = useStore(
(state) => ({ bears: state.bears, fishes: state.fishes }),
shallow
)
return (
<div>
<p>Bears: {bears}</p>
<p>Fishes: {fishes}</p>
</div>
)
}使用useShallow
Zustand提供了useShallow钩子简化shallow比较:
jsx
import { useShallow } from 'zustand/react/shallow'
function Component() {
const { bears, fishes } = useStore(
useShallow((state) => ({ bears: state.bears, fishes: state.fishes }))
)
return (
<div>
<p>Bears: {bears}</p>
<p>Fishes: {fishes}</p>
</div>
)
}最佳实践
组织Store
对于大型应用,可以将store分割成多个slice:
jsx
import { create } from 'zustand'
// 创建用户slice
const createUserSlice = (set) => ({
user: null,
loading: false,
error: null,
login: async (credentials) => {
try {
set({ loading: true, error: null })
// 登录逻辑
const user = await loginApi(credentials)
set({ user, loading: false })
} catch (error) {
set({ error: error.message, loading: false })
}
},
logout: () => set({ user: null }),
})
// 创建购物车slice
const createCartSlice = (set, get) => ({
items: [],
totalItems: 0,
totalPrice: 0,
addItem: (item) => {
set((state) => {
const newItems = [...state.items, item]
return {
items: newItems,
totalItems: newItems.length,
totalPrice: newItems.reduce((sum, item) => sum + item.price, 0),
}
})
},
removeItem: (id) => {
set((state) => {
const newItems = state.items.filter(item => item.id !== id)
return {
items: newItems,
totalItems: newItems.length,
totalPrice: newItems.reduce((sum, item) => sum + item.price, 0),
}
})
},
clearCart: () => set({ items: [], totalItems: 0, totalPrice: 0 }),
})
// 组合所有slice创建store
const useStore = create((set, get) => ({
...createUserSlice(set, get),
...createCartSlice(set, get),
}))使用Immer简化状态更新
使用Immer可以简化复杂状态的更新:
jsx
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
const useStore = create(
immer((set) => ({
nested: {
structure: {
count: 0,
},
},
// 使用Immer可以直接修改状态
increment: () => set((state) => {
state.nested.structure.count += 1
}),
addTodo: (todo) => set((state) => {
state.todos.push(todo)
}),
}))
)测试
测试Zustand store:
jsx
import { create } from 'zustand'
import { renderHook, act } from '@testing-library/react-hooks'
// 创建store
const createStore = () =>
create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}))
describe('Bear store', () => {
it('should increase population', () => {
const { result } = renderHook(() => createStore())
act(() => {
result.current.getState().increasePopulation()
})
expect(result.current.getState().bears).toBe(1)
})
it('should remove all bears', () => {
const { result } = renderHook(() => createStore())
act(() => {
result.current.getState().increasePopulation()
result.current.getState().increasePopulation()
result.current.getState().removeAllBears()
})
expect(result.current.getState().bears).toBe(0)
})
})与其他状态管理库对比
Zustand vs Redux
| 特性 | Zustand | Redux |
|---|---|---|
| 体积 | 轻量级 (~3KB) | 较大 |
| 样板代码 | 极少 | 较多 |
| 学习曲线 | 简单 | 陡峭 |
| 中间件 | 内置支持 | 内置支持 |
| DevTools | 支持 | 支持 |
| 性能 | 优秀 | 优秀 |
| TypeScript | 完全支持 | 完全支持 |
| 社区生态 | 成长中 | 成熟 |
Zustand vs Context API
| 特性 | Zustand | Context API |
|---|---|---|
| 性能 | 优秀,避免不必要的重渲染 | 可能导致不必要的重渲染 |
| 复杂度 | 低 | 中等 |
| 状态更新 | 简单直观 | 需要自行实现 |
| 中间件 | 内置支持 | 不支持 |
| 持久化 | 内置支持 | 需要自行实现 |
| 全局访问 | 支持 | 需要Provider包裹 |
总结
Zustand是一个简单而强大的状态管理库,它提供了:
- 简洁的API,减少样板代码
- 出色的性能和灵活性
- 丰富的中间件支持
- 与React的无缝集成
- TypeScript的完全支持
对于中小型React应用,Zustand是一个理想的状态管理解决方案,它平衡了简单性和功能性,让开发者可以专注于业务逻辑而不是状态管理的复杂性。