名称:zustand-高级模式 用户可调用:false 描述:当实现Zustand高级模式时使用,包括瞬态更新、使用选择器的订阅、存储组合和性能优化技术。 允许工具:
- 读取
- 写入
- 编辑
- Bash
- Grep
- Glob
Zustand - 高级模式
使用Zustand构建复杂应用程序的高级技术和模式,包括瞬态更新、乐观更新和复杂的状态管理策略。
关键概念
瞬态更新
更新状态而不触发重新渲染:
const useStore = create((set) => ({
count: 0,
increment: () =>
set((state) => ({ count: state.count + 1 }), false, 'increment'),
}))
// 用法:更新而不触发重新渲染
useStore.setState({ count: 10 }, true) // replace: true, skip re-render
使用选择器的订阅
订阅状态的特定部分:
const useStore = create<Store>()((set) => ({ /* ... */ }))
// 仅订阅计数变化
const unsubscribe = useStore.subscribe(
(state) => state.count,
(count, prevCount) => {
console.log(`计数从 ${prevCount} 变为 ${count}`)
},
{
equalityFn: (a, b) => a === b,
fireImmediately: false,
}
)
最佳实践
1. 乐观更新
立即更新UI,然后与服务器同步:
interface TodoStore {
todos: Todo[]
addTodo: (text: string) => Promise<void>
updateTodo: (id: string, text: string) => Promise<void>
deleteTodo: (id: string) => Promise<void>
}
const useTodoStore = create<TodoStore>()((set, get) => ({
todos: [],
addTodo: async (text) => {
const optimisticTodo = {
id: `temp-${Date.now()}`,
text,
completed: false,
}
// 乐观更新
set((state) => ({
todos: [...state.todos, optimisticTodo],
}))
try {
const savedTodo = await api.createTodo({ text })
// 用真实待办事项替换乐观待办事项
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === optimisticTodo.id ? savedTodo : todo
),
}))
} catch (error) {
// 错误时回滚
set((state) => ({
todos: state.todos.filter((todo) => todo.id !== optimisticTodo.id),
}))
throw error
}
},
updateTodo: async (id, text) => {
const previousTodos = get().todos
// 乐观更新
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, text } : todo
),
}))
try {
await api.updateTodo(id, { text })
} catch (error) {
// 错误时回滚
set({ todos: previousTodos })
throw error
}
},
deleteTodo: async (id) => {
const previousTodos = get().todos
// 乐观更新
set((state) => ({
todos: state.todos.filter((todo) => todo.id !== id),
}))
try {
await api.deleteTodo(id)
} catch (error) {
// 错误时回滚
set({ todos: previousTodos })
throw error
}
},
}))
2. 撤销/重做模式
实现时间旅行功能:
interface HistoryState<T> {
past: T[]
present: T
future: T[]
}
interface HistoryStore<T> {
history: HistoryState<T>
canUndo: boolean
canRedo: boolean
set: (newPresent: T) => void
undo: () => void
redo: () => void
reset: (initialState: T) => void
}
function createHistoryStore<T>(initialState: T) {
return create<HistoryStore<T>>()((set, get) => ({
history: {
past: [],
present: initialState,
future: [],
},
get canUndo() {
return get().history.past.length > 0
},
get canRedo() {
return get().history.future.length > 0
},
set: (newPresent) =>
set((state) => ({
history: {
past: [...state.history.past, state.history.present],
present: newPresent,
future: [],
},
})),
undo: () =>
set((state) => {
if (state.history.past.length === 0) return state
const previous = state.history.past[state.history.past.length - 1]
const newPast = state.history.past.slice(0, -1)
return {
history: {
past: newPast,
present: previous,
future: [state.history.present, ...state.history.future],
},
}
}),
redo: () =>
set((state) => {
if (state.history.future.length === 0) return state
const next = state.history.future[0]
const newFuture = state.history.future.slice(1)
return {
history: {
past: [...state.history.past, state.history.present],
present: next,
future: newFuture,
},
}
}),
reset: (initialState) =>
set({
history: {
past: [],
present: initialState,
future: [],
},
}),
}))
}
// 用法
interface CanvasState {
shapes: Shape[]
selectedId: string | null
}
const useCanvasStore = createHistoryStore<CanvasState>({
shapes: [],
selectedId: null,
})
function Canvas() {
const { present } = useCanvasStore((state) => state.history)
const { canUndo, canRedo, undo, redo } = useCanvasStore()
return (
<div>
<button onClick={undo} disabled={!canUndo}>
撤销
</button>
<button onClick={redo} disabled={!canRedo}>
重做
</button>
{/* 渲染画布 */}
</div>
)
}
3. 存储组合
组合多个存储:
import { create, StoreApi } from 'zustand'
// 创建可相互访问的绑定存储
function createBoundStore() {
const useAuthStore = create<AuthStore>()((set, get) => ({
user: null,
login: async (credentials) => {
const user = await api.login(credentials)
set({ user })
// 登录后访问购物车存储
const cartStore = stores.cart.getState()
await cartStore.syncCart()
},
logout: () => {
set({ user: null })
// 登出时清空购物车
stores.cart.getState().clearCart()
},
}))
const useCartStore = create<CartStore>()((set, get) => ({
items: [],
addItem: (item) =>
set((state) => ({ items: [...state.items, item] })),
clearCart: () => set({ items: [] }),
syncCart: async () => {
const user = stores.auth.getState().user
if (!user) return
const items = await api.fetchCart(user.id)
set({ items })
},
}))
return {
auth: useAuthStore,
cart: useCartStore,
}
}
const stores = createBoundStore()
export const useAuthStore = stores.auth
export const useCartStore = stores.cart
4. React Context 集成
使用Zustand与React Context实现作用域存储:
import { createContext, useContext, useRef } from 'react'
import { createStore, useStore } from 'zustand'
interface TodoStore {
todos: Todo[]
addTodo: (text: string) => void
toggleTodo: (id: string) => void
}
type TodoStoreApi = ReturnType<typeof createTodoStore>
const createTodoStore = (initialTodos: Todo[] = []) => {
return createStore<TodoStore>()((set) => ({
todos: initialTodos,
addTodo: (text) =>
set((state) => ({
todos: [
...state.todos,
{ id: Date.now().toString(), text, completed: false },
],
})),
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
),
})),
}))
}
const TodoStoreContext = createContext<TodoStoreApi | null>(null)
export function TodoStoreProvider({
children,
initialTodos,
}: {
children: React.ReactNode
initialTodos?: Todo[]
}) {
const storeRef = useRef<TodoStoreApi>()
if (!storeRef.current) {
storeRef.current = createTodoStore(initialTodos)
}
return (
<TodoStoreContext.Provider value={storeRef.current}>
{children}
</TodoStoreContext.Provider>
)
}
export function useTodoStore<T>(selector: (state: TodoStore) => T): T {
const store = useContext(TodoStoreContext)
if (!store) {
throw new Error('useTodoStore必须在TodoStoreProvider内使用')
}
return useStore(store, selector)
}
// 用法
function App() {
return (
<TodoStoreProvider initialTodos={[]}>
<TodoList />
</TodoStoreProvider>
)
}
function TodoList() {
const todos = useTodoStore((state) => state.todos)
const addTodo = useTodoStore((state) => state.addTodo)
return (
<div>
{todos.map((todo) => (
<div key={todo.id}>{todo.text}</div>
))}
<button onClick={() => addTodo('新待办事项')}>添加</button>
</div>
)
}
5. 使用选择器的派生状态
创建记忆化派生状态:
import { create } from 'zustand'
import { shallow } from 'zustand/shallow'
interface Store {
items: Item[]
filter: 'all' | 'active' | 'completed'
sortBy: 'name' | 'date'
}
const useStore = create<Store>()((set) => ({ /* ... */ }))
// 记忆化选择器
const selectFilteredAndSortedItems = (state: Store) => {
let items = state.items
// 过滤
if (state.filter === 'active') {
items = items.filter((item) => !item.completed)
} else if (state.filter === 'completed') {
items = items.filter((item) => item.completed)
}
// 排序
if (state.sortBy === 'name') {
items = [...items].sort((a, b) => a.name.localeCompare(b.name))
} else {
items = [...items].sort((a, b) => b.date.getTime() - a.date.getTime())
}
return items
}
// 用法
function ItemList() {
const items = useStore(selectFilteredAndSortedItems)
return <div>{items.map((item) => <Item key={item.id} item={item} />)}</div>
}
示例
WebSocket 集成
interface ChatStore {
messages: Message[]
isConnected: boolean
connect: () => void
disconnect: () => void
sendMessage: (text: string) => void
}
const useChatStore = create<ChatStore>()((set, get) => {
let ws: WebSocket | null = null
return {
messages: [],
isConnected: false,
connect: () => {
ws = new WebSocket('wss://chat.example.com')
ws.onopen = () => {
set({ isConnected: true })
}
ws.onmessage = (event) => {
const message = JSON.parse(event.data)
set((state) => ({
messages: [...state.messages, message],
}))
}
ws.onclose = () => {
set({ isConnected: false })
}
ws.onerror = (error) => {
console.error('WebSocket错误:', error)
set({ isConnected: false })
}
},
disconnect: () => {
ws?.close()
ws = null
set({ isConnected: false })
},
sendMessage: (text) => {
if (!ws || ws.readyState !== WebSocket.OPEN) return
const message = {
id: Date.now().toString(),
text,
timestamp: new Date(),
userId: 'current-user',
}
ws.send(JSON.stringify(message))
// 乐观添加到消息
set((state) => ({
messages: [...state.messages, message],
}))
},
}
})
分页模式
interface PaginatedStore<T> {
items: T[]
page: number
pageSize: number
total: number
isLoading: boolean
hasMore: boolean
fetchPage: (page: number) => Promise<void>
nextPage: () => Promise<void>
prevPage: () => Promise<void>
reset: () => void
}
function createPaginatedStore<T>(
fetcher: (page: number, pageSize: number) => Promise<{ items: T[]; total: number }>,
pageSize: number = 20
) {
return create<PaginatedStore<T>>()((set, get) => ({
items: [],
page: 1,
pageSize,
total: 0,
isLoading: false,
get hasMore() {
const { page, pageSize, total } = get()
return page * pageSize < total
},
fetchPage: async (page) => {
set({ isLoading: true })
try {
const { items, total } = await fetcher(page, get().pageSize)
set({ items, page, total, isLoading: false })
} catch (error) {
set({ isLoading: false })
throw error
}
},
nextPage: async () => {
const { page, hasMore } = get()
if (!hasMore) return
await get().fetchPage(page + 1)
},
prevPage: async () => {
const { page } = get()
if (page <= 1) return
await get().fetchPage(page - 1)
},
reset: () =>
set({
items: [],
page: 1,
total: 0,
isLoading: false,
}),
}))
}
// 用法
const useProductStore = createPaginatedStore<Product>(
async (page, pageSize) => {
const response = await fetch(
`/api/products?page=${page}&pageSize=${pageSize}`
)
return response.json()
}
)
使用Getter的计算属性
interface Store {
items: Item[]
filter: string
sortBy: string
// 计算属性
filteredItems: Item[]
sortedItems: Item[]
stats: {
total: number
completed: number
active: number
}
}
const useStore = create<Store>()((set, get) => ({
items: [],
filter: 'all',
sortBy: 'date',
get filteredItems() {
const { items, filter } = get()
if (filter === 'all') return items
if (filter === 'completed') return items.filter((i) => i.completed)
return items.filter((i) => !i.completed)
},
get sortedItems() {
const { filteredItems, sortBy } = get()
const items = [...filteredItems]
if (sortBy === 'name') {
return items.sort((a, b) => a.name.localeCompare(b.name))
}
return items.sort((a, b) => b.date.getTime() - a.date.getTime())
},
get stats() {
const { items } = get()
return {
total: items.length,
completed: items.filter((i) => i.completed).length,
active: items.filter((i) => !i.completed).length,
}
},
}))
常见模式
批量更新
原子性地更新多个存储:
function batchUpdates(updates: Array<() => void>) {
updates.forEach((update) => update())
}
// 用法
batchUpdates([
() => useAuthStore.setState({ user: newUser }),
() => useCartStore.setState({ items: [] }),
() => useNotificationStore.setState({ unread: 0 }),
])
错误边界集成
interface ErrorStore {
errors: Error[]
addError: (error: Error) => void
clearErrors: () => void
}
const useErrorStore = create<ErrorStore>()((set) => ({
errors: [],
addError: (error) =>
set((state) => ({ errors: [...state.errors, error] })),
clearErrors: () => set({ errors: [] }),
}))
// 错误边界
function ErrorBoundary({ children }: { children: React.ReactNode }) {
const errors = useErrorStore((state) => state.errors)
if (errors.length > 0) {
return <div>错误:{errors[0].message}</div>
}
return <>{children}</>
}
反模式
❌ 不要存储派生状态
// 坏:存储派生状态
const useStore = create((set) => ({
items: [],
itemCount: 0, // ❌ 冗余
addItem: (item) =>
set((state) => ({
items: [...state.items, item],
itemCount: state.items.length + 1, // ❌ 手动同步
})),
}))
// 好:使用getter用于派生状态
const useStore = create((set, get) => ({
items: [],
get itemCount() {
return get().items.length
},
addItem: (item) =>
set((state) => ({ items: [...state.items, item] })),
}))
❌ 不要创建循环依赖
// 坏:循环依赖
const useStoreA = create((set) => ({
value: 0,
update: () => {
useStoreB.getState().sync() // ❌ 循环
},
}))
const useStoreB = create((set) => ({
value: 0,
sync: () => {
useStoreA.getState().update() // ❌ 循环
},
}))
❌ 不要过度使用订阅
// 坏:在每个组件中订阅
function Component() {
useEffect(() => {
const unsubscribe = useStore.subscribe((state) => {
console.log(state) // ❌ 如果未清理会导致内存泄漏
})
// 缺少return unsubscribe
}, [])
}
// 好:改用选择器
function Component() {
const value = useStore((state) => state.value)
return <div>{value}</div>
}
相关技能
- zustand-store-patterns:基本存储创建和使用
- zustand-typescript:TypeScript集成
- zustand-middleware:使用中间件增强功能