2026年5月30日 01:39

How to create custom middleware in Zustand?

Creating custom middleware in Zustand is very flexible and can be used to implement various features like logging, state validation, performance monitoring, etc.

Basic custom middleware structure:

javascript
const customMiddleware = (config) => (set, get, api) => { // Logic executed before original store const originalSet = set; // Wrap set function const wrappedSet = (partial, replace) => { // Execute logic before state update console.log('State will update:', partial); // Call original set const result = originalSet(partial, replace); // Execute logic after state update console.log('State updated:', get()); return result; }; // Create store const store = config(wrappedSet, get, api); // Return enhanced store return store; };

Example 1: Logger middleware

javascript
const loggerMiddleware = (config) => (set, get, api) => { const originalSet = set; const wrappedSet = (partial, replace) => { const previousState = get(); const result = originalSet(partial, replace); const nextState = get(); console.log('Previous state:', previousState); console.log('Action:', partial); console.log('Next state:', nextState); return result; }; return config(wrappedSet, get, api); }; // Usage const useStore = create( loggerMiddleware((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })) })) );

Example 2: State validation middleware

javascript
const validationMiddleware = (schema) => (config) => (set, get, api) => { const originalSet = set; const wrappedSet = (partial, replace) => { // Validate state update const newState = typeof partial === 'function' ? partial(get()) : partial; const validation = schema.safeParse({ ...get(), ...newState }); if (!validation.success) { console.error('State validation failed:', validation.error); throw new Error('Invalid state update'); } return originalSet(partial, replace); }; return config(wrappedSet, get, api); }; // Usage import { z } from 'zod'; const storeSchema = z.object({ count: z.number().min(0), user: z.object({ id: z.string(), name: z.string().min(1) }).nullable() }); const useStore = create( validationMiddleware(storeSchema)((set) => ({ count: 0, user: null, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: Math.max(0, state.count - 1) })) })) );

Example 3: Performance monitoring middleware

javascript
const performanceMiddleware = (config) => (set, get, api) => { const originalSet = set; const renderCounts = {}; const wrappedSet = (partial, replace) => { const startTime = performance.now(); const result = originalSet(partial, replace); const endTime = performance.now(); const duration = endTime - startTime; if (duration > 10) { console.warn(`Slow state update: ${duration.toFixed(2)}ms`, partial); } return result; }; const store = config(wrappedSet, get, api); // Track component render counts const originalSubscribe = api.subscribe; api.subscribe = (listener, selector) => { const wrappedListener = (state, previousState) => { const key = selector ? selector.toString() : 'full-store'; renderCounts[key] = (renderCounts[key] || 0) + 1; if (renderCounts[key] % 10 === 0) { console.log(`Render count for ${key}:`, renderCounts[key]); } listener(state, previousState); }; return originalSubscribe(wrappedListener, selector); }; return store; }; // Usage const useStore = create( performanceMiddleware((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })) })) );

Example 4: Undo/Redo middleware

javascript
const undoRedoMiddleware = (config) => (set, get, api) => { let history = []; let future = []; const MAX_HISTORY = 50; const originalSet = set; const wrappedSet = (partial, replace) => { const previousState = get(); const result = originalSet(partial, replace); const nextState = get(); // Save to history history.push(previousState); if (history.length > MAX_HISTORY) { history.shift(); } // Clear future future = []; return result; }; const store = config(wrappedSet, get, api); // Add undo functionality store.undo = () => { if (history.length === 0) return; const previousState = history.pop(); future.push(get()); originalSet(previousState, true); }; // Add redo functionality store.redo = () => { if (future.length === 0) return; const nextState = future.pop(); history.push(get()); originalSet(nextState, true); }; // Clear history store.clearHistory = () => { history = []; future = []; }; return store; }; // Usage const useStore = create( undoRedoMiddleware((set, get) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })) })) ); // Use in component function Counter() { const { count, increment, decrement } = useStore(); const undo = useStore((state) => state.undo); const redo = useStore((state) => state.redo); return ( <div> <h1>Count: {count}</h1> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> <button onClick={undo}>Undo</button> <button onClick={redo}>Redo</button> </div> ); }

Key points:

  • Custom middleware is a higher-order function that receives config and returns a new configuration function
  • Can wrap set, get, and api to enhance functionality
  • Middleware execution order is important, outer middleware executes first
  • Can add additional features in middleware like logging, validation, performance monitoring
  • Middleware can return enhanced store with new methods or properties
标签:ReactZustand