← 中文资源

简体中文版本

React useEffect 闭包过期 — 缺少依赖

通过在依赖中列出 effect 内使用的每个响应式值,或用 ref 读取最新值以避免重跑,修复 effect 中过期值问题。

阅读英文原文

症状

本篇指南处理“React useEffect 闭包过期 — 缺少依赖”。请从症状出发,按顺序执行诊断,在推进前确认每个假设;随后通过修复与验证步骤闭环。

可观察到的症状包括:

  • effect 一直使用属性或状态的初始值
  • 定时器每次 tick 打印相同的计数
  • lint 规则 react-hooks/exhaustive-deps 警告缺少依赖

诊断

先在隔离环境中复现故障,再按顺序执行以下检查,逐步缩小范围。

步骤 1

npx eslint src --rule '{"react-hooks/exhaustive-deps": "warn"}'

该规则会确切指出数组中缺失哪个依赖。

步骤 2

git diff src/hooks/useTicker.js

每次运行记录捕获值,可看出是初始值还是最新值。

常见诱因(按出现频率排序):

  • 依赖数组遗漏了 effect 内读取的值
  • 状态之后会变化却使用空依赖数组
  • 回调捕获了旧值且未重建

根因

每次渲染都会创建新闭包;effect 捕获创建它那次渲染的值。若所用值不在依赖数组中,effect 会保留旧闭包并读取过期值。

修复

把 effect 内使用的每个响应式值列入依赖,或把最新值放入 ref 并在内部读取 ref.current;不得无理由关闭 exhaustive-deps。

注意:加入每次渲染都变化的依赖会使 effect 循环;应用 useCallback 或 useMemo 稳定该值。

function Ticker() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount((c) => c + 1);   // functional update avoids stale count
    }, 1000);
    return () => clearInterval(id);
  }, []); // safe: no external value read

  return <span>{count}</span>;
}

// reading latest state without re-running
const latest = useRef(count);
useEffect(() => { latest.current = count; }, [count]);

验证

运行该场景并确认 effect 读到最新值;lint 规则应不再报告缺失依赖。

修复后补充一条回归测试,复现原始症状,防止问题再次出现。

这类故障通常是环境问题而非纯逻辑错误,因此在缺陷记录中应写明完整命令、环境变量与工作目录。请用最小的脚本复现,待最小用例清楚后再扩大范围。为这类故障准备一份简短的排查清单,能显著减少下次排障时间。