# 调试神器：Redux DevTools

URL: https://caijiao.org/zustand/guide/devtools
Source: docs/zustand/guide/devtools.md
Description: 了解如何在 Zustand 中集成和使用 Redux DevTools 进行状态调试，包括安装配置、基本用法、命名 Action、与其他中间件结合使用、高级功能以及最佳实践。

[Zustand](../) 提供了与 [Redux DevTools](https://github.com/reduxjs/redux-devtools) 浏览器扩展的无缝集成，使你能够可视化地调试和监控 Store 的状态变化。Redux DevTools 是一个强大的调试工具，它可以帮助你：

- **查看状态历史**：追踪状态的每一次变化
- **时间旅行调试**：回溯到之前的状态，重现 bug
- **检查 action**：查看触发状态变化的 action 及其参数
- **比较状态差异**：查看两次状态变化之间的具体差异
- **导出/导入状态**：保存和恢复应用状态

## 安装与基本使用

### 安装 Redux DevTools 浏览器扩展

首先，你需要在浏览器中安装 Redux DevTools 扩展：

- **Chrome**：[Redux DevTools - Chrome Web Store](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd)
- **Firefox**：[Redux DevTools - Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/)
- **Edge**：[Redux DevTools - Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/redux-devtools/nnkgneoiohoecpdiaponcejilbhhikei)

### 在 Zustand 中集成 DevTools

Zustand 提供了一个 `devtools` 中间件，用于与 Redux DevTools 集成：

```javascript
import { create } from 'zustand'
import { devtools } from 'zustand/middleware' // 导入 devtools 中间件

// 创建一个带有 devtools 中间件的 Store
const useCounterStore = create(
  devtools(
    (set, get) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
      decrement: () => set((state) => ({ count: state.count - 1 })),
      reset: () => set({ count: 0 }),
    }),
    {
      name: 'counter-store', // DevTools 中的实例名称
    }
  )
)
```

### 在组件中使用

```jsx
function Counter() {
  const { count, increment, decrement, reset } = useCounterStore()

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  )
}
```

## 使用 Redux DevTools 调试

安装并配置好 DevTools 后，你可以通过以下步骤使用它来调试 Zustand Store：

1. **打开 DevTools**：在浏览器中按 F12 打开开发者工具，切换到 "Redux" 或 "Redux DevTools" 标签页
2. **查看状态历史**：每次触发 action 时，DevTools 会记录一条新的状态变更记录
3. **检查状态**：在 "State" 面板中查看当前的完整状态
4. **检查 action**：在 "Action" 面板中查看触发状态变化的 action 及其参数
5. **时间旅行调试**：点击历史记录中的任意一条记录，应用会回溯到该状态
6. **比较差异**：选择两条历史记录，点击 "Diff" 按钮查看状态差异

## 配置选项

`devtools` 中间件提供了丰富的配置选项：

```javascript
const useStore = create(
  devtools(
    (set, get) => ({
      // Store 定义
    }),
    {
      name: 'my-store', // DevTools 中的实例名称
      enabled: true, // 是否启用 DevTools
      anonymousActionType: 'anonymous-action', // 匿名 action 的类型名称
      serialize: { // 序列化配置
        options: {
          // JSON.stringify 的配置选项
          undefined: true,
          function: true,
          symbol: true,
          map: true,
          set: true,
        },
      },
    }
  )
)
```

### 主要配置选项说明

| 选项 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| `name` | string | - | DevTools 中的实例名称，用于区分多个 Store |
| `enabled` | boolean | true | 是否启用 DevTools |
| `anonymousActionType` | string | 'anonymous-action' | 匿名 action 的类型名称 |
| `serialize` | object | - | 序列化配置，用于控制状态的序列化方式 |

## 命名 Action

默认情况下，Zustand 会为 action 生成匿名名称。你可以通过在 `set` 函数中提供 action 名称来为 action 命名：

```javascript
const useTodoStore = create(
  devtools(
    (set, get) => ({
      todos: [],
      
      // 命名 action：'addTodo'
      addTodo: (text) => set(
        (state) => ({ todos: [...state.todos, { id: Date.now(), text, completed: false }] }),
        false, // 是否应该替换整个状态
        'addTodo' // action 名称
      ),
      
      // 命名 action：'toggleTodo'
      toggleTodo: (id) => set(
        (state) => ({
          todos: state.todos.map((todo) =>
            todo.id === id ? { ...todo, completed: !todo.completed } : todo
          )
        }),
        false,
        'toggleTodo'
      ),
    }),
    { name: 'todo-store' }
  )
)
```

为 action 命名可以使 DevTools 中的调试信息更加清晰和易于理解。

## 与其他中间件结合使用

`devtools` 中间件可以与其他 Zustand 中间件（如 `persist` 和 `immer`）结合使用：

```javascript
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'

const useTodoStore = create(
  persist(
    devtools(
      immer((set, get) => ({
        todos: [],
        addTodo: (text) => set((state) => {
          state.todos.push({ id: Date.now(), text, completed: false })
        }, false, 'addTodo'), // 命名 action
        toggleTodo: (id) => set((state) => {
          const todo = state.todos.find((todo) => todo.id === id)
          if (todo) {
            todo.completed = !todo.completed
          }
        }, false, 'toggleTodo'), // 命名 action
      })),
      { name: 'todo-store' }
    ),
    { name: 'todo-storage' }
  )
)
```

**注意**：中间件的顺序很重要，一般建议将 `devtools` 放在其他中间件的内层，这样可以捕获到所有中间件处理后的 action 和状态变化。

## 高级用法

### 时间旅行调试

时间旅行调试是 Redux DevTools 最强大的功能之一，它允许你回溯到之前的状态：

1. 在 DevTools 中选择一条历史记录
2. 应用会自动回溯到该状态
3. 你可以在该状态下继续调试，重现 bug

### 状态差异比较

DevTools 允许你比较两次状态变化之间的具体差异：

1. 在 DevTools 中按住 Ctrl 键（或 Cmd 键）选择两条历史记录
2. 点击 "Diff" 按钮
3. DevTools 会显示这两次状态之间的具体差异

### 导出/导入状态

你可以导出当前状态，稍后再导入：

1. 在 DevTools 中点击 "Export" 按钮导出状态
2. 稍后点击 "Import" 按钮导入之前导出的状态
3. 应用会恢复到导入的状态

### 禁用特定 Action

你可以通过配置禁用特定的 action：

```javascript
const useStore = create(
  devtools(
    (set, get) => ({
      // Store 定义
      sensitiveAction: () => set((state) => {
        // 敏感操作，不希望在 DevTools 中显示
        return { ...state, sensitiveData: '...' }
      }),
    }),
    {
      name: 'my-store',
      // 禁用特定的 action
      enabled: (state, action) => {
        return action.type !== 'sensitiveAction'
      },
    }
  )
)
```

## 性能考虑

虽然 Redux DevTools 提供了强大的调试功能，但在生产环境中，你可能希望禁用它以提高性能：

```javascript
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'

const useStore = create(
  devtools(
    (set, get) => ({
      // Store 定义
    }),
    {
      name: 'my-store',
      enabled: process.env.NODE_ENV !== 'production', // 只在开发环境启用
    }
  )
)
```

## 最佳实践总结

1. **为 Action 命名**：为 action 提供清晰的名称，使调试信息更加易于理解
2. **只在开发环境启用**：在生产环境中禁用 DevTools 以提高性能
3. **与其他中间件结合使用**：根据需求，将 DevTools 与 `persist`、`immer` 等中间件结合使用
4. **保护敏感数据**：避免在 DevTools 中显示敏感数据
5. **合理使用时间旅行**：利用时间旅行功能重现和调试 bug
6. **导出/导入状态**：使用导出/导入功能保存和恢复应用状态

## 常见问题与解决方案

### 1. DevTools 中看不到状态变化

**问题**：在组件中触发了 action，但在 DevTools 中看不到状态变化

**解决方案**：

- 确保已安装 Redux DevTools 浏览器扩展
- 确保已正确导入并使用 `devtools` 中间件
- 确保 `enabled` 配置选项为 `true`
- 检查中间件的顺序是否正确

### 2. Action 名称显示为 "anonymous-action"

**问题**：在 DevTools 中，所有 action 的名称都显示为 "anonymous-action"

**解决方案**：为 action 提供名称：

```javascript
addTodo: (text) => set(
  (state) => ({ todos: [...state.todos, { id: Date.now(), text, completed: false }] }),
  false,
  'addTodo' // 为 action 命名
)
```

### 3. 状态显示为 "[object Object]"

**问题**：在 DevTools 中，状态显示为 "[object Object]" 而不是具体的状态内容

**解决方案**：检查序列化配置，确保状态可以被正确序列化：

```javascript
devtools(
  (set, get) => ({
    // Store 定义
  }),
  {
    name: 'my-store',
    serialize: {
      options: {
        undefined: true,
        function: true,
        symbol: true,
      },
    },
  }
)
```

### 4. 生产环境性能问题

**问题**：在生产环境中，应用性能下降

**解决方案**：在生产环境中禁用 DevTools：

```javascript
devtools(
  (set, get) => ({
    // Store 定义
  }),
  {
    name: 'my-store',
    enabled: process.env.NODE_ENV !== 'production',
  }
)
```

在接下来的章节中，我们将学习 [TypeScript 类型定义指南](typescript.md)，了解如何在 Zustand 中使用 TypeScript 进行类型定义。
