开发调试 #

Redux DevTools 集成 #

Zustand 可以与 Redux DevTools 浏览器扩展集成,提供强大的调试功能。

基本配置 #

tsx
import { create } from 'zustand'
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' }
  )
)

安装 Redux DevTools #

  1. Chrome: Redux DevTools Extension
  2. Firefox: Redux DevTools
  3. Edge: Redux DevTools

DevTools 配置选项 #

tsx
devtools(
  (set) => ({ /* store */ }),
  {
    name: 'MyStore',              // Store 名称
    enabled: true,                // 是否启用
    anonymousActionType: '更新',   // 匿名 action 名称
    store: 'my-store',            // DevTools 中的 store 名称
    trace: true,                  // 启用调用栈追踪
    traceLimit: 25,               // 追踪深度
  }
)

Action 命名 #

方式一:使用第三个参数 #

tsx
const useStore = create(
  devtools(
    (set) => ({
      count: 0,
      
      increment: () => set(
        (state) => ({ count: state.count + 1 }),
        false,
        'increment'  // action 名称
      ),
      
      add: (amount: number) => set(
        (state) => ({ count: state.count + amount }),
        false,
        `add/${amount}`  // 动态名称
      ),
    }),
    { name: 'CounterStore' }
  )
)

方式二:使用对象 #

tsx
const useStore = create(
  devtools(
    (set) => ({
      count: 0,
      
      increment: () => set(
        (state) => ({ count: state.count + 1 }),
        false,
        { type: 'INCREMENT', payload: 1 }
      ),
      
      add: (amount: number) => set(
        (state) => ({ count: state.count + amount }),
        false,
        { type: 'ADD', payload: amount }
      ),
    }),
    { name: 'CounterStore' }
  )
)

多个 Store #

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

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

日志调试 #

自定义日志中间件 #

tsx
const log = (config) => (set, get, api) =>
  config(
    (args) => {
      console.group('State Update')
      console.log('%c Previous State:', 'color: #9E9E9E', get())
      set(args)
      console.log('%c Next State:', 'color: #4CAF50', get())
      console.groupEnd()
    },
    get,
    api
  )

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

带时间戳的日志 #

tsx
const timestampLog = (config) => (set, get, api) =>
  config(
    (args) => {
      const timestamp = new Date().toISOString()
      console.log(`[${timestamp}] State Update:`, {
        prev: get(),
        next: typeof args === 'function' ? args(get()) : args,
      })
      set(args)
    },
    get,
    api
  )

条件日志 #

tsx
const conditionalLog = (shouldLog = () => true) => (config) => (set, get, api) =>
  config(
    (args) => {
      if (shouldLog(get(), args)) {
        console.log('State Update:', get(), '->', args)
      }
      set(args)
    },
    get,
    api
  )

// 只在开发环境记录
const useStore = create(
  conditionalLog(() => process.env.NODE_ENV === 'development')(
    (set) => ({ /* store */ })
  )
)

状态快照 #

保存状态快照 #

tsx
const useStore = create((set, get) => ({
  count: 0,
  history: [] as number[],
  
  increment: () => set((state) => ({ 
    count: state.count + 1,
    history: [...state.history, state.count]
  })),
  
  undo: () => set((state) => {
    const lastState = state.history[state.history.length - 1]
    return {
      count: lastState,
      history: state.history.slice(0, -1)
    }
  }),
}))

时间旅行调试 #

tsx
interface State {
  count: number
  snapshots: number[]
  currentIndex: number
  
  increment: () => void
  saveSnapshot: () => void
  travelTo: (index: number) => void
}

const useStore = create<State>((set, get) => ({
  count: 0,
  snapshots: [0],
  currentIndex: 0,
  
  increment: () => {
    set((state) => ({ count: state.count + 1 }))
    get().saveSnapshot()
  },
  
  saveSnapshot: () => set((state) => ({
    snapshots: [...state.snapshots.slice(0, state.currentIndex + 1), state.count],
    currentIndex: state.snapshots.length,
  })),
  
  travelTo: (index) => set((state) => ({
    count: state.snapshots[index],
    currentIndex: index,
  })),
}))

React DevTools #

使用 React DevTools 检查 #

tsx
import { useEffect } from 'react'

function DebugStore() {
  const state = useStore()
  
  useEffect(() => {
    console.log('Store State:', state)
  }, [state])
  
  return null
}

// 在 App 中使用
function App() {
  return (
    <>
      <DebugStore />
      {/* 其他组件 */}
    </>
  )
}

自定义 Hook 调试 #

tsx
function useDebugStore(selector, name = 'Store') {
  const state = useStore(selector)
  
  useEffect(() => {
    console.log(`[${name}] State:`, state)
  }, [state, name])
  
  return state
}

// 使用
function Component() {
  const count = useDebugStore((state) => state.count, 'Counter')
  return <div>{count}</div>
}

控制台调试 #

在控制台中访问 Store #

tsx
// 将 store 暴露到全局
if (typeof window !== 'undefined') {
  (window as any).__STORE__ = useStore
}

// 在控制台中使用
// __STORE__.getState()      // 获取状态
// __STORE__.setState({})    // 设置状态
// __STORE__.subscribe()     // 订阅变化

创建调试工具函数 #

tsx
const createDebugTools = (store: any, name: string) => {
  const tools = {
    name,
    getState: store.getState,
    setState: store.setState,
    subscribe: store.subscribe,
    
    log: () => console.log(`[${name}]`, store.getState()),
    
    reset: () => store.setState(store.getInitialState?.() || {}),
    
    time: (fn: () => void) => {
      console.time(name)
      fn()
      console.timeEnd(name)
    },
    
    profile: (fn: () => void) => {
      console.profile(name)
      fn()
      console.profileEnd(name)
    },
  }
  
  if (typeof window !== 'undefined') {
    (window as any)[`__${name.toUpperCase()}__`] = tools
  }
  
  return tools
}

// 使用
createDebugTools(useStore, 'counter')

// 在控制台
// __COUNTER__.log()
// __COUNTER__.reset()

错误追踪 #

错误边界 #

tsx
import { Component, ReactNode } from 'react'

interface Props {
  children: ReactNode
  store: any
}

interface State {
  hasError: boolean
  error: Error | null
}

class StoreErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props)
    this.state = { hasError: false, error: null }
  }
  
  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error }
  }
  
  componentDidCatch(error: Error, errorInfo: any) {
    console.error('Store Error:', error)
    console.error('State:', this.props.store.getState())
    console.error('Component Stack:', errorInfo.componentStack)
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h2>状态错误</h2>
          <pre>{this.state.error?.message}</pre>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            重试
          </button>
        </div>
      )
    }
    
    return this.props.children
  }
}

// 使用
function App() {
  return (
    <StoreErrorBoundary store={useStore}>
      <MyApp />
    </StoreErrorBoundary>
  )
}

状态验证 #

tsx
import { z } from 'zod'

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

const validateState = (config) => (set, get, api) =>
  config(
    (args) => {
      const newState = typeof args === 'function' ? args(get()) : args
      const result = stateSchema.safeParse(newState)
      
      if (!result.success) {
        console.error('State validation failed:', result.error)
        return
      }
      
      set(args)
    },
    get,
    api
  )

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

性能调试 #

渲染追踪 #

tsx
const useRenderCount = (name: string) => {
  const count = useRef(0)
  count.current++
  
  useEffect(() => {
    console.log(`[${name}] Render count:`, count.current)
  })
}

function Component() {
  useRenderCount('MyComponent')
  const count = useStore((state) => state.count)
  return <div>{count}</div>
}

选择器性能 #

tsx
import { useEffect, useRef } from 'react'

function useDebugSelector(selector: (state: any) => any, name: string) {
  const prevRef = useRef<any>()
  const value = useStore(selector)
  
  useEffect(() => {
    if (prevRef.current !== value) {
      console.log(`[${name}] Selector changed:`, {
        prev: prevRef.current,
        next: value,
      })
    }
    prevRef.current = value
  }, [value, name])
  
  return value
}

更新频率监控 #

tsx
const updateMonitor = (config) => (set, get, api) => {
  let updateCount = 0
  let lastTime = Date.now()
  
  return config(
    (args) => {
      updateCount++
      const now = Date.now()
      
      if (now - lastTime >= 1000) {
        console.log(`Updates per second: ${updateCount}`)
        updateCount = 0
        lastTime = now
      }
      
      set(args)
    },
    get,
    api
  )
}

实际案例 #

完整的调试配置 #

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

const isDev = process.env.NODE_ENV === 'development'

const useStore = create(
  devtools(
    persist(
      (set, get) => ({
        count: 0,
        name: '',
        
        increment: () => set(
          (state) => ({ count: state.count + 1 }),
          false,
          'increment'
        ),
        
        setName: (name: string) => set(
          { name },
          false,
          'setName'
        ),
        
        reset: () => set(
          { count: 0, name: '' },
          false,
          'reset'
        ),
      }),
      {
        name: 'app-storage',
        storage: createJSONStorage(() => localStorage),
      }
    ),
    {
      name: 'AppStore',
      enabled: isDev,
      trace: isDev,
    }
  )
)

// 开发环境暴露调试工具
if (isDev && typeof window !== 'undefined') {
  (window as any).__STORE__ = {
    getState: useStore.getState,
    setState: useStore.setState,
    subscribe: useStore.subscribe,
    reset: () => useStore.setState({ count: 0, name: '' }),
  }
}

最佳实践 #

1. 条件启用 DevTools #

tsx
devtools(
  store,
  {
    name: 'MyStore',
    enabled: process.env.NODE_ENV === 'development',
  }
)

2. 为 Actions 命名 #

tsx
// ✅ 好:有意义的名称
set({ count: 0 }, false, 'reset')

// ❌ 不好:匿名更新
set({ count: 0 })

3. 使用错误边界 #

tsx
<StoreErrorBoundary store={useStore}>
  <App />
</StoreErrorBoundary>

4. 生产环境移除调试代码 #

tsx
const log = process.env.NODE_ENV === 'development'
  ? (msg: string) => console.log(msg)
  : () => {}

总结 #

开发调试的关键点:

  • 使用 Redux DevTools 进行状态追踪
  • 自定义日志中间件记录状态变化
  • 使用错误边界捕获状态错误
  • 在控制台暴露调试接口
  • 监控性能和渲染频率

接下来,让我们学习 测试策略,掌握单元测试和集成测试技巧。

最后更新:2026-03-28