中间件系统 #

什么是中间件? #

中间件是一种在状态更新前后插入自定义逻辑的机制。Zustand 的中间件系统非常灵活,可以用于日志记录、状态持久化、不可变数据处理等场景。

中间件结构 #

tsx
type Middleware = (
  config: StateCreator
) => (set: SetState, get: GetState, api: StoreApi) => State

// 简化理解
const myMiddleware = (config) => (set, get, api) => {
  // 自定义逻辑
  return config(set, get, api)
}

使用中间件 #

基本用法 #

tsx
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'

const useStore = create(
  devtools(
    persist(
      (set) => ({
        count: 0,
        increment: () => set((state) => ({ count: state.count + 1 })),
      }),
      { name: 'my-store' }
    )
  )
)

中间件执行顺序 #

text
请求 → devtools → persist → immer → 原始 store
响应 ← devtools ← persist ← immer ← 原始 store

内置中间件 #

devtools - 开发工具 #

与 Redux DevTools 集成,方便调试:

tsx
import { devtools } from 'zustand/middleware'

const useStore = create(
  devtools(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
      decrement: () => set((state) => ({ count: state.count - 1 })),
    }),
    { name: 'CounterStore' }
  )
)

devtools 配置选项 #

tsx
devtools(
  (set) => ({ /* store */ }),
  {
    name: 'MyStore',           // Store 名称
    enabled: true,             // 是否启用
    anonymousActionType: '匿名操作', // 匿名 action 名称
    store: 'my-store',         // Redux DevTools 中的 store 名称
  }
)

为 Action 命名 #

tsx
const useStore = create(
  devtools(
    (set) => ({
      count: 0,
      
      // 使用第三个参数命名 action
      increment: () => set(
        (state) => ({ count: state.count + 1 }),
        false,
        'increment'
      ),
      
      // 或使用对象形式
      add: (amount: number) => set(
        (state) => ({ count: state.count + amount }),
        false,
        { type: 'add', amount }
      ),
    }),
    { name: 'CounterStore' }
  )
)

persist - 状态持久化 #

将状态保存到本地存储:

tsx
import { persist, createJSONStorage } from 'zustand/middleware'

interface State {
  count: number
  increment: () => void
}

const useStore = create<State>()(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
    }),
    {
      name: 'counter-storage', // 存储名称
    }
  )
)

persist 配置选项 #

tsx
persist(
  (set) => ({ /* store */ }),
  {
    name: 'my-storage',          // 存储名称(必需)
    storage: createJSONStorage(() => sessionStorage), // 存储方式
    partialize: (state) => ({    // 只持久化部分状态
      count: state.count,
    }),
    onRehydrateStorage: () => (state) => {
      console.log('状态已恢复', state)
    },
    version: 1,                  // 版本号
    migrate: (persisted, version) => {
      // 迁移逻辑
      if (version === 0) {
        return { ...persisted, newField: 'default' }
      }
      return persisted
    },
    merge: (persisted, current) => ({
      ...current,
      ...persisted,
    }),
  }
)

选择存储引擎 #

tsx
// localStorage(默认)
persist(store, {
  name: 'storage',
  storage: createJSONStorage(() => localStorage),
})

// sessionStorage
persist(store, {
  name: 'storage',
  storage: createJSONStorage(() => sessionStorage),
})

// 自定义存储
persist(store, {
  name: 'storage',
  storage: {
    getItem: (name) => {
      const value = customStorage.get(name)
      return value ?? null
    },
    setItem: (name, value) => {
      customStorage.set(name, value)
    },
    removeItem: (name) => {
      customStorage.remove(name)
    },
  },
})

immer - 不可变数据处理 #

简化复杂状态的更新:

tsx
import { immer } from 'zustand/middleware/immer'

interface State {
  user: {
    name: string
    settings: {
      theme: 'light' | 'dark'
      notifications: boolean
    }
  }
  updateUser: (name: string) => void
  updateTheme: (theme: 'light' | 'dark') => void
}

const useStore = create<State>()(
  immer((set) => ({
    user: {
      name: 'John',
      settings: {
        theme: 'light',
        notifications: true,
      },
    },
    
    // 直接"修改"状态
    updateUser: (name) => set((state) => {
      state.user.name = name
    }),
    
    updateTheme: (theme) => set((state) => {
      state.user.settings.theme = theme
    }),
  }))
)

redux - Redux 兼容 #

使用 Redux 风格的 reducer:

tsx
import { redux } from 'zustand/middleware'

type State = { count: number }
type Action = { type: 'INCREMENT' } | { type: 'DECREMENT' }

const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 }
    case 'DECREMENT':
      return { count: state.count - 1 }
    default:
      return state
  }
}

const useStore = create(
  redux(reducer, { count: 0 })
)

// 使用
function Component() {
  const { count, dispatch } = useStore()
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
    </div>
  )
}

combine - 类型推断增强 #

改进 TypeScript 类型推断:

tsx
import { combine } from 'zustand/middleware'

const useStore = create(
  combine({ count: 0 }, (set) => ({
    increment: () => set((state) => ({ count: state.count + 1 })),
    decrement: () => set((state) => ({ count: state.count - 1 })),
  }))
)

subscribeWithSelector - 高级订阅 #

支持选择器订阅:

tsx
import { subscribeWithSelector } from 'zustand/middleware'

const useStore = create(
  subscribeWithSelector((set) => ({
    count: 0,
    name: 'test',
    increment: () => set((state) => ({ count: state.count + 1 })),
  }))
)

// 在组件外订阅特定状态变化
useStore.subscribe(
  (state) => state.count,
  (count) => {
    console.log('Count changed:', count)
  }
)

自定义中间件 #

日志中间件 #

tsx
const logger = (config) => (set, get, api) =>
  config(
    (args) => {
      console.log('旧状态:', get())
      set(args)
      console.log('新状态:', get())
    },
    get,
    api
  )

const useStore = create(
  logger((set) => ({
    count: 0,
    increment: () => set((state) => ({ count: state.count + 1 })),
  }))
)

时间戳中间件 #

tsx
const timestamp = (config) => (set, get, api) =>
  config(
    (args) => {
      set({
        ...args,
        updatedAt: Date.now(),
      })
    },
    get,
    api
  )

const useStore = create(
  timestamp((set) => ({
    count: 0,
    updatedAt: null,
    increment: () => set((state) => ({ count: state.count + 1 })),
  }))
)

验证中间件 #

tsx
const validator = (schema) => (config) => (set, get, api) =>
  config(
    (args) => {
      const newState = typeof args === 'function' ? args(get()) : args
      
      // 验证新状态
      const result = schema.safeParse(newState)
      if (!result.success) {
        console.error('验证失败:', result.error)
        return
      }
      
      set(args)
    },
    get,
    api
  )

// 使用 Zod 验证
import { z } from 'zod'

const schema = z.object({
  count: z.number().min(0).max(100),
  name: z.string(),
})

const useStore = create(
  validator(schema)((set) => ({
    count: 0,
    name: '',
    increment: () => set((state) => ({ count: state.count + 1 })),
  }))
)

节流中间件 #

tsx
const throttle = (ms: number) => (config) => (set, get, api) => {
  let lastCall = 0
  
  return config(
    (args) => {
      const now = Date.now()
      if (now - lastCall >= ms) {
        lastCall = now
        set(args)
      }
    },
    get,
    api
  )
}

const useStore = create(
  throttle(1000)((set) => ({
    count: 0,
    increment: () => set((state) => ({ count: state.count + 1 })),
  }))
)

重置中间件 #

tsx
const reset = (initialState) => (config) => (set, get, api) =>
  config(
    (args) => set(args),
    get,
    {
      ...api,
      reset: () => set(initialState),
    }
  )

const initialState = { count: 0, name: '' }

const useStore = create(
  reset(initialState)((set) => ({
    ...initialState,
    increment: () => set((state) => ({ count: state.count + 1 })),
  }))
)

// 使用
useStore.getState().reset()

组合中间件 #

多个中间件组合 #

tsx
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'

const useStore = create(
  devtools(
    persist(
      immer((set) => ({
        user: {
          name: '',
          settings: {
            theme: 'light' as 'light' | 'dark',
          },
        },
        
        updateName: (name: string) => set((state) => {
          state.user.name = name
        }),
        
        updateTheme: (theme: 'light' | 'dark') => set((state) => {
          state.user.settings.theme = theme
        }),
      })),
      { name: 'user-storage' }
    ),
    { name: 'UserStore' }
  )
)

中间件工厂函数 #

tsx
function createLoggerMiddleware(name: string) {
  return (config) => (set, get, api) =>
    config(
      (args) => {
        console.log(`[${name}] 旧状态:`, get())
        set(args)
        console.log(`[${name}] 新状态:`, get())
      },
      get,
      api
    )
}

const useUserStore = create(
  createLoggerMiddleware('UserStore')((set) => ({
    user: null,
    setUser: (user) => set({ user }),
  }))
)

const useCartStore = create(
  createLoggerMiddleware('CartStore')((set) => ({
    items: [],
    addItem: (item) => set((state) => ({ items: [...state.items, item] })),
  }))
)

实际案例 #

完整配置的 Store #

tsx
import { create } from 'zustand'
import { devtools, persist, createJSONStorage } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'

interface Todo {
  id: string
  text: string
  completed: boolean
  createdAt: number
}

interface TodoState {
  todos: Todo[]
  filter: 'all' | 'active' | 'completed'
  
  addTodo: (text: string) => void
  toggleTodo: (id: string) => void
  removeTodo: (id: string) => void
  setFilter: (filter: TodoState['filter']) => void
  clearCompleted: () => void
}

const useTodoStore = create<TodoState>()(
  devtools(
    persist(
      immer((set) => ({
        todos: [],
        filter: 'all',
        
        addTodo: (text) => set((state) => {
          state.todos.push({
            id: Date.now().toString(),
            text,
            completed: false,
            createdAt: Date.now(),
          })
        }),
        
        toggleTodo: (id) => set((state) => {
          const todo = state.todos.find((t) => t.id === id)
          if (todo) {
            todo.completed = !todo.completed
          }
        }),
        
        removeTodo: (id) => set((state) => {
          const index = state.todos.findIndex((t) => t.id === id)
          if (index !== -1) {
            state.todos.splice(index, 1)
          }
        }),
        
        setFilter: (filter) => set({ filter }),
        
        clearCompleted: () => set((state) => {
          state.todos = state.todos.filter((t) => !t.completed)
        }),
      })),
      {
        name: 'todo-storage',
        storage: createJSONStorage(() => localStorage),
        partialize: (state) => ({
          todos: state.todos,
          filter: state.filter,
        }),
        version: 1,
        migrate: (persisted: any, version) => {
          if (version === 0) {
            return {
              ...persisted,
              todos: persisted.todos.map((t: any) => ({
                ...t,
                createdAt: Date.now(),
              })),
            }
          }
          return persisted
        },
      }
    ),
    { name: 'TodoStore' }
  )
)

最佳实践 #

1. 中间件顺序 #

tsx
// ✅ 推荐顺序
create(
  devtools(      // 最外层:调试
    persist(     // 中间层:持久化
      immer(     // 内层:数据处理
        (set) => ({ /* store */ })
      )
    )
  )
)

2. 条件启用中间件 #

tsx
const useStore = create(
  process.env.NODE_ENV === 'development'
    ? devtools(store, { name: 'MyStore' })
    : store
)

3. 类型安全 #

tsx
// 使用类型断言确保类型安全
const useStore = create<State>()(
  persist(
    (set) => ({ /* store */ }),
    { name: 'storage' }
  )
)

总结 #

中间件系统是 Zustand 的强大特性:

  • devtools:调试支持
  • persist:状态持久化
  • immer:简化不可变更新
  • redux:Redux 兼容
  • 可以自定义中间件扩展功能

接下来,让我们深入学习 状态持久化,掌握数据本地存储技巧。

最后更新:2026-03-28