前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库React useEffect 订阅清理内存泄漏
ReReact副作用与同步

React Effect 忘记清理订阅,路由切换后会出现什么问题?

外部事件源不会因为 React 组件卸载就自动忘记订阅;遗漏清理可能留下重复处理器与旧资源。

前端进阶之旅 · 一题精讲更新于 2026.09.06
React#副作用与同步#Effect#路由
先看核心答案读代码示例
理解线索

一次路由往返怎样积累重复监听

  1. 进入页面Effect 向外部事件源注册回调
  2. 离开页面若未取消,外部源仍可能持有原回调
  3. 再次进入新回调与旧回调同时响应同一个事件

并非每个未清理操作都会永久泄漏,但不能只靠没有控制台警告判断资源管理正确。

核心回答

先记住这个答案

Effect 建立的全局监听、定时器或外部订阅具有自己的生命周期。组件离开页面后若没有解除,外部源可能继续持有回调,路由回来再建立一份后,同一事件就可能被处理多次,并继续读取旧闭包中的业务参数。cleanup 应释放本轮建立的资源,使用对应取消函数、监听器引用或句柄;验证时要覆盖反复进入、参数切换和卸载后触发事件。

  • 卸载 React 组件不等于外部订阅自动注销
  • 移除监听需要匹配事件类型、函数引用与 capture
  • 清理应释放本轮资源,并检查卸载后的实际行为

重复处理不只是性能问题

例如一个文档页面监听 Escape 关闭自己的帮助提示。第一次进入建立一个监听,离开时没有移除,第二次进入又建立另一个。后续按键可能同时运行新旧回调;若回调还记录文档 ID 或触发业务命令,就可能对已经离开的页面执行错误操作。

外部事件源持有函数,函数又可能通过闭包引用数据或对象,从而延长它们的存活时间。具体是否形成持续内存增长,要看引用链与资源生命周期,不能把每个迟到回调都直接称作永久泄漏。可见的重复请求、重复提示和错误参数,本身已经足以说明需要修复。

清理要拿到同一轮注册使用的函数

下面示例在 Effect 内定义 handleKey,并在 cleanup 中使用同一个函数引用移除监听。事件类型与 capture 参数也保持一致。若注册和移除时分别写两个内容相同的箭头函数,它们仍是不同对象,旧监听不会因为函数体相似而被找到。

这个组件将 onDismiss 作为依赖,调用方换了处理函数时会先清理旧监听再建立新监听。若调用方的函数频繁变化,可以再分析是否需要稳定引用或调整职责,但不能直接漏掉依赖。示例只处理帮助提示,不承担完整弹窗的焦点管理与无障碍行为。

配对注册与移除 Escape 监听JSX
import { useEffect } from 'react';

export default function HelpHint({ onDismiss }) {
  useEffect(() => {
    function handleKey(event) {
      if (event.key === 'Escape') onDismiss();
    }
    window.addEventListener('keydown', handleKey, false);
    return () => window.removeEventListener('keydown', handleKey, false);
  }, [onDismiss]);

  return <aside>按 Escape 关闭这条帮助提示。</aside>;
}

在 React 客户端挂载后,Escape 会调用传入的 onDismiss;卸载后,本组件注册的处理器应不再响应。示例没有控制台输出,验收需要实际按键或派发键盘事件。

用可重复的进入离开流程验证修复

先进入页面并触发一次事件,记录业务处理次数,再离开并触发同样事件,确认已离开页面的处理器不再工作。随后重复进入多次,检查一次事件仍只产生当前这份订阅对应的处理。参数切换也要覆盖,确保旧参数的回调被替换而不是继续并存。

对于连接池或多个组件共享的服务,cleanup 通常应取消自己的订阅份额,而不是直接关闭整个全局连接。请求取消也不能保证服务器已经停止执行;迟到结果仍可能需要轮次判断。内存分析可以作为进一步证据,但首要验收是外部行为与资源所有权符合预期。

回答前,多想一步

容易答错的地方

removeEventListener 只要写相同函数内容就行
函数引用必须对应注册时的监听器,事件类型与 capture 也要匹配。保留本轮函数引用或使用 API 提供的取消句柄,比在清理中重新构造函数可靠。
用 ref 阻止第二次 Effect 建立即可解决
这可能掩盖开发检查或重新挂载暴露的问题,却没有解除首次建立的外部订阅。应该使建立和清理正确配对,确保真实路由往返也不会积累资源。
试着用自己的话回答

面试官还会怎么问?

setInterval 需要怎样清理?

保留本轮 setInterval 返回的句柄,在 cleanup 中调用 clearInterval。若计时回调依赖响应式输入,也要同时处理依赖与闭包,清理定时器并不会自动修复读旧值。

使用 AbortController 能移除事件监听吗?

支持 signal 选项的事件监听可以绑定 AbortSignal,并在清理时中止对应 controller。仍要保证 controller 属于本轮资源,不要误中止其它消费者共享的操作。

没有 React 的卸载更新警告就代表没泄漏吗?

不代表。警告行为与版本及操作有关,而外部回调可能仍然重复请求、持有引用或改变其它系统。应验证监听数量、业务行为与资源生命周期,不能只观察控制台是否安静。

从一道题,走向一组知识

把知识连起来

副作用与同步

React Effect 挂载、依赖更新和卸载时怎样建立与清理?

明确清理除了卸载,还会发生在依赖变化后的重同步之前。

副作用与同步

useEffect 为什么必须声明读到的响应式依赖,漏写会怎样?

清理正确之外,也需要保证订阅回调使用的输入与依赖一致。

参考资料

  • Synchronizing with Effects
  • EventTarget.removeEventListener

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 重复处理不只是性能问题
  3. 清理要拿到同一轮注册使用的函数
  4. 用可重复的进入离开流程验证修复
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑