名称: zustand-middleware 用户可调用: false 描述: 当实现Zustand中间件以增强存储功能,包括持久化、开发工具、不可变性等时使用。涵盖持久化、devtools、immer和自定义中间件。 允许工具:
- 读取
- 写入
- 编辑
- Bash
- Grep
- Glob
Zustand - 中间件
Zustand提供强大的中间件来增强存储功能,包括持久化存储、Redux DevTools集成、使用Immer进行不可变更新等。
关键概念
中间件组合
中间件包装存储创建函数:
import { create } from 'zustand'
import { persist, devtools } from 'zustand/middleware'
const useStore = create(
devtools(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{ name: 'counter-storage' }
)
)
)
顺序重要
从内到外应用中间件:
// ✅ 正确顺序
create(devtools(persist(immer(...))))
// devtools包装persist包装immer包装你的存储
最佳实践
1. 持久化中间件
保存和恢复存储状态到localStorage或其他存储:
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
interface CartStore {
items: CartItem[]
addItem: (item: CartItem) => void
removeItem: (id: string) => void
clearCart: () => void
}
const useCartStore = create<CartStore>()(
persist(
(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: [] }),
}),
{
name: 'shopping-cart',
storage: createJSONStorage(() => localStorage),
}
)
)
持久化选项
persist(
(set) => ({ /* 存储 */ }),
{
name: 'my-store', // 存储键的唯一名称
storage: createJSONStorage(() => localStorage), // 或sessionStorage
partialize: (state) => ({ count: state.count }), // 仅持久化特定字段
onRehydrateStorage: (state) => {
console.log('水合开始')
return (state, error) => {
if (error) {
console.log('水合期间错误', error)
} else {
console.log('水合完成')
}
}
},
version: 1,
migrate: (persistedState, version) => {
// 处理版本迁移
if (version === 0) {
// 将旧状态迁移到新格式
}
return persistedState
},
}
)
2. DevTools中间件
与Redux DevTools集成进行调试:
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
interface Store {
count: number
increment: () => void
decrement: () => void
}
const useStore = create<Store>()(
devtools(
(set) => ({
count: 0,
increment: () =>
set((state) => ({ count: state.count + 1 }), false, 'increment'),
decrement: () =>
set((state) => ({ count: state.count - 1 }), false, 'decrement'),
}),
{ name: 'CounterStore' }
)
)
DevTools选项
devtools(
(set) => ({ /* 存储 */ }),
{
name: 'MyStore', // 在devtools中的名称
enabled: process.env.NODE_ENV === 'development', // 有条件启用
anonymousActionType: 'action', // 默认操作名称
trace: true, // 包含堆栈跟踪
}
)
3. Immer中间件
使用可变语法编写不可变更新:
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
interface TodoStore {
todos: Todo[]
addTodo: (text: string) => void
toggleTodo: (id: string) => void
updateTodo: (id: string, text: string) => void
}
const useTodoStore = create<TodoStore>()(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
state.todos.push({
id: Date.now().toString(),
text,
completed: false,
})
}),
toggleTodo: (id) =>
set((state) => {
const todo = state.todos.find((t) => t.id === id)
if (todo) {
todo.completed = !todo.completed
}
}),
updateTodo: (id, text) =>
set((state) => {
const todo = state.todos.find((t) => t.id === id)
if (todo) {
todo.text = text
}
}),
}))
)
4. 订阅
在React外部监听状态变化:
const useStore = create<Store>()((set) => ({ /* ... */ }))
// 订阅所有变化
const unsubscribe = useStore.subscribe((state, prevState) => {
console.log('状态变化:', state)
})
// 订阅特定值
const unsubscribe = useStore.subscribe(
(state) => state.count,
(count, prevCount) => {
console.log('计数从', prevCount, '变为', count)
}
)
// 清理
unsubscribe()
5. 组合多个中间件
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'
interface Store {
count: number
todos: Todo[]
increment: () => void
addTodo: (text: string) => void
}
const useStore = create<Store>()(
devtools(
persist(
immer((set) => ({
count: 0,
todos: [],
increment: () =>
set((state) => {
state.count++
}),
addTodo: (text) =>
set((state) => {
state.todos.push({
id: Date.now().toString(),
text,
completed: false,
})
}),
})),
{
name: 'app-storage',
partialize: (state) => ({
count: state.count,
todos: state.todos,
}),
}
),
{ name: 'AppStore' }
)
)
示例
自定义日志中间件
import { StateCreator, StoreMutatorIdentifier } from 'zustand'
type Logger = <
T,
Mps extends [StoreMutatorIdentifier, unknown][] = [],
Mcs extends [StoreMutatorIdentifier, unknown][] = []
>(
f: StateCreator<T, Mps, Mcs>,
name?: string
) => StateCreator<T, Mps, Mcs>
type LoggerImpl = <T>(
f: StateCreator<T, [], []>,
name?: string
) => StateCreator<T, [], []>
const loggerImpl: LoggerImpl = (f, name) => (set, get, store) => {
const loggedSet: typeof set = (...a) => {
set(...a)
console.log(...(name ? [`${name}:`] : []), get())
}
store.setState = loggedSet
return f(loggedSet, get, store)
}
export const logger = loggerImpl as unknown as Logger
// 用法
const useStore = create<Store>()(
logger(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
'CounterStore'
)
)
自定义重置中间件
import { StateCreator, StoreMutatorIdentifier } from 'zustand'
type Resettable = <
T,
Mps extends [StoreMutatorIdentifier, unknown][] = [],
Mcs extends [StoreMutatorIdentifier, unknown][] = []
>(
f: StateCreator<T, Mps, Mcs>
) => StateCreator<T, Mps, Mcs>
type ResettableImpl = <T>(
f: StateCreator<T, [], []>
) => StateCreator<T, [], []>
const resettableImpl: ResettableImpl = (f) => (set, get, store) => {
const initialState = f(set, get, store)
store.reset = () => set(initialState)
return initialState
}
export const resettable = resettableImpl as unknown as Resettable
// 扩展存储类型
declare module 'zustand' {
interface StoreApi<T> {
reset?: () => void
}
}
// 用法
const useStore = create<Store>()(
resettable((set) => ({
count: 0,
name: '',
increment: () => set((state) => ({ count: state.count + 1 })),
setName: (name) => set({ name }),
}))
)
// 重置到初始状态
useStore.reset()
IndexedDB持久化
import { StateStorage } from 'zustand/middleware'
import { get, set, del } from 'idb-keyval'
const indexedDBStorage: StateStorage = {
getItem: async (name: string): Promise<string | null> => {
return (await get(name)) || null
},
setItem: async (name: string, value: string): Promise<void> => {
await set(name, value)
},
removeItem: async (name: string): Promise<void> => {
await del(name)
},
}
const useStore = create<Store>()(
persist(
(set) => ({
largeData: [],
addData: (data) =>
set((state) => ({ largeData: [...state.largeData, data] })),
}),
{
name: 'large-data-storage',
storage: createJSONStorage(() => indexedDBStorage),
}
)
)
React Native的异步存储
import AsyncStorage from '@react-native-async-storage/async-storage'
import { StateStorage } from 'zustand/middleware'
const asyncStorage: StateStorage = {
getItem: async (name: string): Promise<string | null> => {
return await AsyncStorage.getItem(name)
},
setItem: async (name: string, value: string): Promise<void> => {
await AsyncStorage.setItem(name, value)
},
removeItem: async (name: string): Promise<void> => {
await AsyncStorage.removeItem(name)
},
}
const useStore = create<Store>()(
persist(
(set) => ({ /* ... */ }),
{
name: 'app-storage',
storage: createJSONStorage(() => asyncStorage),
}
)
)
常见模式
条件持久化
仅持久化某些字段:
const useStore = create<Store>()(
persist(
(set) => ({
// 持久化
theme: 'light',
language: 'en',
// 不持久化
isLoading: false,
error: null,
setTheme: (theme) => set({ theme }),
setLanguage: (language) => set({ language }),
}),
{
name: 'settings',
partialize: (state) => ({
theme: state.theme,
language: state.language,
}),
}
)
)
版本迁移
处理持久化状态的破坏性变化:
const useStore = create<Store>()(
persist(
(set) => ({ /* ... */ }),
{
name: 'app-store',
version: 2,
migrate: (persistedState: any, version: number) => {
if (version === 0) {
// 从版本0迁移到1
persistedState.newField = 'default'
}
if (version === 1) {
// 从版本1迁移到2
persistedState.items = persistedState.oldItems.map((item: any) => ({
id: item.id,
name: item.title, // 重命名字段
}))
delete persistedState.oldItems
}
return persistedState as Store
},
}
)
)
水合检测
知道持久化状态何时加载:
const useStore = create<Store>()(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{
name: 'counter',
onRehydrateStorage: () => (state) => {
console.log('状态水合:', state)
},
}
)
)
// 在组件中
function App() {
const [hydrated, setHydrated] = useState(false)
useEffect(() => {
useStore.persist.onFinishHydration(() => {
setHydrated(true)
})
}, [])
if (!hydrated) {
return <div>加载中...</div>
}
return <div>应用内容</div>
}
反模式
❌ 不要持久化敏感数据
// 错误:在localStorage中持久化令牌
const useAuthStore = create(
persist(
(set) => ({
token: null,
user: null,
login: async (credentials) => {
const { token, user } = await api.login(credentials)
set({ token, user }) // ❌ 令牌在localStorage中
},
}),
{ name: 'auth' }
)
)
// 正确:使用安全存储或不持久化令牌
const useAuthStore = create(
persist(
(set) => ({
user: null,
login: async (credentials) => {
const { token, user } = await api.login(credentials)
secureStorage.setToken(token) // ✅ 安全存储
set({ user })
},
}),
{
name: 'auth',
partialize: (state) => ({ user: state.user }), // ✅ 仅持久化用户
}
)
)
❌ 不要忽略中间件顺序
// 错误:DevTools无法看到持久化的初始状态
create(persist(devtools(...)))
// 正确:DevTools可以看到完整的状态生命周期
create(devtools(persist(...)))
❌ 不要在没有Immer的情况下突变状态
// 错误:直接突变而不使用immer
const useStore = create((set) => ({
items: [],
addItem: (item) =>
set((state) => {
state.items.push(item) // ❌ 直接突变
return state
}),
}))
// 正确:使用immer中间件
const useStore = create(
immer((set) => ({
items: [],
addItem: (item) =>
set((state) => {
state.items.push(item) // ✅ 使用immer安全
}),
}))
)
❌ 不要忘记清理订阅
// 错误:内存泄漏
useEffect(() => {
useStore.subscribe((state) => {
console.log(state)
})
}, [])
// 正确:清理订阅
useEffect(() => {
const unsubscribe = useStore.subscribe((state) => {
console.log(state)
})
return unsubscribe
}, [])
相关技能
- zustand-store-patterns: 基本存储创建和使用
- zustand-typescript: TypeScript与中间件集成
- zustand-advanced-patterns: 自定义中间件和高级技术