跳转到主要内容

Debugging

The key is to reduce the problem space: turn a weird outcome into a testable hypothesis.

When you feel stuck, it’s usually because too many variables are moving at once. Your job is to freeze variables until the bug becomes deterministic.

A repeatable workflow

  1. Reproduce reliably.
  2. Freeze inputs and isolate the smallest failing unit.
  3. Prove or disprove one hypothesis at a time.

This sounds slow, but it’s the fastest path to a real fix. Debugging is a search problem: constrain the search space.

Common sources of bugs

  • Stale closures: reading old state/props inside async callbacks.
  • Effects with wrong dependencies: missing deps, or doing too much inside one Effect.
  • Race conditions: multiple requests updating the same state out of order.
  • Assuming “setState is immediate”: state is a snapshot for the current render.

React Strict Mode note

In development, React may run effects twice (`mount` → `cleanup` → `mount`) to help you find missing cleanups. If a bug only happens in dev, check your cleanup and idempotency.

A reliable heuristic: if your effect starts something (timer, subscription, request), it must stop it in cleanup.

Trace state and effects

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);
}

Make logs actionable

Prefer structured logs over scattered `console.log`. Include correlation ids and durations so you can reconstruct the timeline.

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;
    },
  };
}

Isolate and bisect

When you can’t reason about the full system, stop trying. Replace unknowns with constants and remove half the code until the behavior becomes explainable.

  • Remove half of the component tree or logic branches to see if the bug remains.
  • Freeze inputs: hardcode props/state/network responses to make behavior deterministic.
  • Turn timing bugs into data bugs: add `abort`, request ids, and ignore stale results.

Production checklist

  • You can reproduce the bug with a minimal scenario and steps.
  • Inputs and outputs are logged with correlation ids.
  • Effects are idempotent and have correct cleanup.
  • Async flows handle race conditions (abort/ignore stale).

Further reading

Debugging - Guides - React Docs - React 文档