useEffect 与依赖
useEffect 与依赖 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
Effect 连上外部系统之后,还得回答「什么时候重新连」。读完你应该能写完整的依赖数组让定时订阅随配置重建,并亲手复现、诊断漏依赖造成的闭包旧值问题。
问题场景
一个心跳组件,每 interval 毫秒跳一次,间隔由用户调整:
useEffect(() => {
const id = setInterval(() => setTicks(t => t + 1), interval);
return () => clearInterval(id);
}, []); // 故意写空:定时器只会建一次
用户把间隔从 1000ms 调成 200ms,界面上的「每 1000ms 一跳」却纹丝不动——依赖数组是空的,Effect 只在挂载时跑过一次,它记住的还是旧的 interval。
核心解释
Effect 的契约是:依赖数组声明「这个 Effect 的逻辑依赖哪些渲染期数据」。React 拿它判断「外部世界的连接需不需要重建」——数组里每个值变了,先执行上一次的清理,再重跑 Effect:
useEffect(() => {
const id = setInterval(() => setTicks(t => t + 1), interval);
return () => clearInterval(id);
}, [interval]); // interval 变 → 清旧定时器 → 建新的
两个配套纪律:
- 依赖必须写全。只要 Effect 逻辑里读了的渲染期值(state、props、context)都该进数组,这是给 React 的诚实声明。写全之后,linter 能帮你查漏,React 能保证 Effect 读到的永远是最新值。
- 想「只跑一次」也别撒谎。空数组是「逻辑不依赖任何渲染期值」的声明。真需要只建一次连接,用 ref 存可变值,而不是把依赖藏起来。
漏依赖的代价不止「不刷新」,还有更隐蔽的闭包旧值。Effect 的回调函数在创建时捕获了当时的变量;依赖缺失时它一直活着,捕获的一直是老快照:
useEffect(() => {
const id = setInterval(() => setLog(l => [...l, count]), 1000);
return () => clearInterval(id);
}, []); // 漏了 count:回调里的 count 永远是挂载时的 0
外面按钮把 count 点到 5,日志里还是一排 0。函数没变,函数看见的世界就永远不变。
示例拆解
实验区上下两块:
- 心跳区:三个按钮切换 200 / 500 / 1000ms,
interval作为 props 传入,Effect 依赖[interval]。切换瞬间旧定时器被清理、新定时器建立,文案里的「每 X ms 一跳」和实际节奏永远一致——可以在控制台感受节奏变化; - 旧值区:每秒把
count写进日志的定时器故意漏了依赖,旁边有按钮可以给 count 加一。点几次再看日志:外部 count 已经涨了,日志里还是一排 0; - 修复方向就在下方任务里:把 count 加入依赖后,定时器会每秒重建一次(日志功能本身有更好的写法,用函数式更新),这是「写全依赖」的直观代价与正确性保证;
- 验证方式:调心跳频率看文案跟随;点三次「+1」再观察日志区是否仍然全是 0。
动手任务
打开本课实验区,完成三个小改动:
- 把旧值区 Effect 的依赖补全成
[count],验证日志开始记录最新值,同时意识到定时器每秒在重建。 - 用函数式更新重写日志逻辑(
setLog(l => [...l, l.length])之类),让依赖数组可以安全保持为空——体会「用更新函数消除对快照的依赖」这条出路。 - 给心跳区加一个「暂停」开关:暂停时不建定时器,恢复时重建(提示:依赖数组里加布尔值)。
常见错误
- 把依赖数组当性能开关手动删减:删掉「不相关」的依赖换来旧值 bug;正确出路是减少依赖(函数式更新、拆分 Effect、用 ref),而不是瞒报。
- 依赖里放对象或函数字面量:
[options]里的 options 每次渲染都是新引用,Effect 每帧重建;传基本类型值或在上游用 useMemo 稳定引用。 - 清理函数漏写:定时器、订阅只建不清,切几次页面就叠了几层重复连接(下一课专门收拾)。
- 以为闭包旧值只在定时器里出现:任何「Effect 创建的、存活超过一次渲染的函数」都有这问题——事件回调、订阅回调、Promise 链都算。
检查与复盘
完成下方检查题确认理解。自查清单:
- 能说清依赖数组的语义:逻辑依赖的渲染期值的诚实清单,变化即重建
- 亲眼确认过漏依赖的旧值现象,并能解释闭包捕获的原因
- 知道两条消除依赖的出路:函数式更新和 ref,而不是删依赖
参考资料
- React 官方文档:使用 Effect 进行同步 —— Effect 生命周期与依赖的官方讲解
- React 官方文档:useEffect API —— 依赖数组规则与常见陷阱清单
读完这一课了?
标记完成后,工作台会安排接下来的学习。