R25第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

副作用清理与生命周期

副作用清理与生命周期 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

Effect 建立连接,清理函数负责断开。读完你应该能实现订阅的注册与清理,说清清理函数的三个触发时机,并用「监听器计数」验证切换页面、卸载组件后不残留重复监听。

问题场景

消息面板挂载时订阅一个消息源,每来一条消息就追加显示。第一版忘了写清理:

function ChatPanel() {
  const [msgs, setMsgs] = useState([]);

  useEffect(() => {
    subscribe(m => setMsgs(prev => [...prev.slice(-4), m]));
    // 没有返回清理函数
  }, []);

  return <div>{msgs.join("、")}</div>;
}

打开面板 → 关掉 → 再打开,消息开始一条顶两条;再开关一轮,一条顶四条。面板明明只挂载了一个,监听回调却在订阅名单里越积越多。

核心解释

订阅这类外部连接,Effect 的标准形态是建立 + 清理成对出现:

useEffect(() => {
  const unsubscribe = subscribe(m => setMsgs(prev => [...prev.slice(-4), m]));
  return unsubscribe; // 返回清理函数
}, []);

清理函数会在三个时机被调用,每个时机都有明确含义:

  • 组件卸载时:面板销毁,断开订阅,不留孤儿监听;
  • 依赖变化、Effect 重跑前:先清理旧连接再建新连接,保证任何时刻只有一份有效订阅(R24 的定时器重建就是这个节奏);
  • 严格模式的开发环境:挂载后会立刻「挂载 → 清理 → 再挂载」走一遍,专门帮你暴露漏清理的 bug——这也是为什么开发时经常看到 Effect 跑了两次。

判断要不要清理的口诀:Effect 让世界多了一个东西(监听器、定时器、连接、占用的资源),清理函数就负责把它收走。收走之后,组件无论怎么挂载、卸载、重跑,外部世界都不会留下残渣。上一课订阅名单里积攒的重复回调,正是「建立」没有配「断开」的直接后果。

还有一个细节值得注意:订阅回调里用的是 setMsgs(prev => [...]) 函数式更新,不依赖渲染快照——这让 Effect 可以安全地只挂载一次,配合空依赖数组成立(R24 的出路在这里落地)。

示例拆解

实验区模拟了一个消息源,并把「订阅名单」直接显示出来:

  • 顶部显示当前监听器数量,每秒广播一条新消息;
  • 「打开聊天面板」挂载 ChatPanel,Effect 里 subscribe;开关按钮反复切换组件的挂载与卸载;
  • 有个开关能在「正确版」和「错误版」之间切换:错误版 subscribe 之后不返回清理函数;正确版返回 unsubscribe;
  • 用错误版开 3 次面板:监听器数量变成 3,面板里每条消息重复显示 3 份——因为 3 个回调都在往同一个列表追加;
  • 切到正确版重新开关:监听器数量回到 1,消息永远只有一份;
  • 验证方式:两种模式各开关 3 次,对比监听器计数和消息重复份数。

动手任务

打开本课实验区,完成三个小改动:

  1. 在 ChatPanel 的 Effect 里加一条日志(或界面提示),分别在「建立订阅」和「清理订阅」时触发,亲眼确认清理时机。
  2. 把面板里的消息列表改成最多保留 6 条(现在是 4 条),并显示「已订阅 / 未订阅」状态。
  3. 给 App 顶层的广播定时器也加上清理(它现在已经在做了),说出如果漏掉、在实验区反复挂载时会观察到什么。

常见错误

  • 清理函数写错返回方式:useEffect(() => { subscribe(fn); return unsubscribe; }) 对;写成 return unsubscribe() 是当场执行订阅立刻断开,回调永远不触发。
  • 清理的是另一个连接:subscribe 返回的引用被覆盖或丢失,清理时断不开原订阅;用变量接住返回值再原样返回。
  • 以为依赖数组为空就不用清理:空数组只说明「连接参数不变」,组件卸载时照样需要断开;清理和依赖频率无关。
  • 在清理函数里再做一次 setState:卸载后更新已卸载组件没有意义还容易出警告;清理只负责对外断开,不负责对内改状态。

检查与复盘

完成下方检查题确认理解。自查清单:

  • 能背出清理函数的三个触发时机:卸载、依赖变化重跑前、严格模式模拟
  • 每写一个建立连接的 Effect,都顺手返回清理函数
  • 会用外部可观察的信号(监听器计数、消息重复份数)验证清理是否干净

参考资料

动手实验
动手实验
预览
点击「运行」在隔离沙箱中预览
检查题共 1 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1单选简单

关于 useEffect 返回的清理函数,下列说法正确的是?

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...