useEffect 依赖项写错常见会引发哪些 bug?如何避免闭包陷阱?
useEffect 依赖写错,最常见会出这几类 bug:
-
值“过期”(stale closure)
effect里拿到旧的state/props,比如定时器里count永远是旧值。 -
不触发更新
漏了依赖,参数变了但effect不重跑,请求/订阅不更新。 -
死循环或频繁重跑
把每次 render 都新建的对象/函数放进依赖,导致反复请求、反复 setState。 -
资源泄漏
重跑时没正确清理,出现重复订阅、重复定时器、内存泄漏。 -
异步竞态
前一次慢请求后返回,覆盖后一次快请求结果(显示“旧数据”)。
避免闭包陷阱的实用做法:
- 开启并遵守
eslint-plugin-react-hooks的exhaustive-deps。 effect里用到的响应式值(props/state)都写进依赖。- 需要“基于上次值更新”时,用函数式更新:
setCount(c => c + 1)。 - 不想因某值变化而重跑
effect,但又要读最新值时,用useRef保存最新值。 - 对对象/函数依赖做稳定化:
useMemo/useCallback。 - 一个
effect只做一件事,拆分副作用,减少错误依赖。 - 异步请求加取消/忽略机制(
AbortController或ignore标记)并在 cleanup 里处理。 - 如果是 React 19,可用
useEffectEvent读取最新值,减少闭包问题。
示例(典型闭包坑):
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // count 是旧值
}, 1000);
return () => clearInterval(id);
}, []); // 漏依赖tsx改法:
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1); // 永远基于最新值
}, 1000);
return () => clearInterval(id);
}, []);tsx