不返回清理函数会导致内存泄漏:异步订阅、定时器、未取消请求、全局事件监听器及第三方库订阅持续持有组件引用,阻止垃圾回收。

在 React 的 useEffect 中不返回清理函数,会导致异步订阅长期持有对组件作用域的引用,无法被垃圾回收。这些订阅本身不会“自动消失”,它们像锚点一样把整个渲染快照(包括 state、props、函数、DOM 节点甚至大型数据)钉在内存里。
定时器类副作用持续运行并捕获旧状态
比如用 setInterval 或 setTimeout 启动一个轮询,但没在清理函数中调用 clearInterval 或 clearTimeout:
- 回调函数形成闭包,捕获了定义它那次渲染的
count、data或ref.current - 即使组件已卸载或重渲染多次,该定时器仍在后台执行,不断读取和打印过时值
- 若回调里还访问了大对象(如
props.userList),整个数组或 Map 就无法释放
未取消的网络请求继续触发无效状态更新
发起 fetch 或 axios 请求后不中断,组件卸载时 Promise 仍处于 pending 状态:
- 请求完成后的
.then回调会尝试调用setState,抛出 “Can't perform a React state update on an unmounted component” 警告 - 更隐蔽的问题是:Promise 内部保留着对
setData、inputRef和响应体的引用,阻止 GC 清理这些值 - 大量未终止请求堆积,可能拖慢主线程,甚至耗尽连接池
全局事件监听器长期驻留并隐式持有组件上下文
通过 window.addEventListener 或 document.addEventListener 绑定监听器,却未配对调用 removeEventListener:
- 监听器函数是闭包,内部引用了
state、dispatch或某个ref,导致整个组件实例无法释放 - 如果监听器绑定在
document上,哪怕页面跳转、组件彻底销毁,它还在默默响应滚动、键盘等事件 - 多个组件重复绑定同名事件(如
resize),却只清理其中一个,其余继续滞留
第三方库订阅未注销,强引用 DOM 或实例对象
使用 Chart.js、D3、RxJS、WebSocket 等时,初始化后没调用 .destroy()、.unsubscribe() 或 .close():
- 库内部可能在 DOM 元素上挂载私有属性,或在全局对象上注册回调,这些都构成强引用链
- 例如 D3 的
selection.on('click', handler)若不显式移除,handler 闭包就一直持有对组件数据的引用 - WebSocket 实例未
.close(),不仅占用 socket 连接,其onmessage回调也会让关联的ref和state无法回收











