跳转到主要内容

调试

调试的关键是缩小问题空间:把“看起来奇怪的结果”还原为“可验证的假设”。

当你感觉卡住时,通常是因为变量同时在变化。你的任务是冻结变量,直到 bug 变得可重复、可验证。

可复用的排查流程

  1. 稳定复现。
  2. 冻结输入,缩到最小失败单元。
  3. 一次只验证一个假设。

这听起来很慢,但其实是最快的“真修复”路径。调试本质是搜索问题:通过约束把搜索空间缩小。

常见 bug 来源

  • 闭包过期:异步回调里读到旧的 `state`/`props`。
  • `Effect` 依赖不正确:漏依赖,或把过多事情塞进一个 `Effect`。
  • 竞态:多个请求同时更新同一份 `state`,写入顺序颠倒。
  • 以为 `setState` 立刻生效:`state` 对当前渲染是“快照”。

React 严格模式提示

开发模式下,React 可能会让 `Effect` 执行两次(`mount` → `cleanup` → 再 `mount`),用于帮助你发现缺失的清理逻辑。如果 bug 只在 dev 发生,先检查清理函数与幂等性。

一个可靠的经验法则是:如果你的 effect “启动了某个东西”(定时器/订阅/请求),就必须在 cleanup 里“停止它”。

追踪状态与副作用

TypeScript
type Event =
  | { type: 'input'; value: string }
  | { type: 'request-start' }
  | { type: 'request-done'; ok: boolean };

export function trace(e: Event) {
  const time = new Date().toISOString();
  console.log(time, e.type, e);
}

让日志可行动

优先结构化日志,避免散落的 `console.log`。带上关联 id 与耗时,才能重建时间线。

TypeScript
type TraceMeta = { scope: string; id?: string };

export function createTracer(meta: TraceMeta) {
  return {
    info(event: string, data?: Record<string, unknown>) {
      console.log(new Date().toISOString(), meta.scope, meta.id ?? '-', event, data ?? {});
    },
    time<T>(event: string, run: () => T) {
      const start = performance.now();
      const result = run();
      const durationMs = Math.round(performance.now() - start);
      console.log(new Date().toISOString(), meta.scope, meta.id ?? '-', event, { durationMs });
      return result;
    },
  };
}

隔离与二分定位

当你无法对全系统推理时,别硬扛:把未知替换成常量,按二分法删掉一半代码,直到行为可解释。

  • 一次砍掉一半组件树或逻辑分支,看问题是否仍存在。
  • 冻结输入:固定 props/state/网络响应,让行为可重复。
  • 把“时序 bug”变成“数据 bug”:加入 `abort`、request id,并忽略过期结果。

上线检查清单

  • 问题有最小复现与明确步骤。
  • 输入/输出日志具备关联 id。
  • `Effect` 幂等且清理正确。
  • 异步流程已处理竞态(`abort`/忽略过期)。

延伸阅读

调试 - Guides - React 文档 - React 文档