React组件卸载后订阅没清理,内存泄漏怎么解决?

Designer°熙然 阅读 17

我在用React做聊天功能,组件里用了WebSocket监听消息。每次切换页面再回来,发现消息会重复收到好几遍,怀疑是之前订阅没清理掉。

我试过在useEffect里return一个清理函数,但好像没生效。比如这样:

useEffect(() => {
  const ws = new WebSocket('ws://example.com');
  ws.onmessage = (e) => setMessage(e.data);
  return () => ws.close();
}, []);

但内存快照里还是能看到旧的监听器残留,是不是哪里写错了?该怎么正确清理订阅?

我来解答 赞 7 收藏
二维码
手机扫码查看
1 条解答
紫瑶的笔记
你的代码看起来是对的,但问题在于WebSocket的清理不够彻底。ws.close()执行了,可onmessage回调可能还挂在事件系统上没清掉。

这样改一下:

useEffect(() => {
const ws = new WebSocket('ws://example.com');

ws.onmessage = (e) => {
setMessage(e.data);
};

return () => {
ws.onmessage = null; // 先解绑回调 ws.close(); // 再关闭连接
};
}, []);


还有个常见坑:如果用了React 18的严格模式,开发环境会先挂载→清理→再挂载一次。你这个代码在第二次挂载时又会新建连接,如果第一次的ws还没彻底关掉,就会出现多个连接同时存在的情况。

可以加个状态防止重复创建:

useEffect(() => {
const ws = new WebSocket('ws://example.com');

ws.onmessage = (e) => {
setMessage(e.data);
};

return () => {
if (ws.readyState === WebSocket.OPEN) {
ws.onmessage = null;
ws.close();
}
};
}, []);


另外提醒一下,setMessage在组件卸载后调用会出warning,虽然不直接导致内存泄漏,但最好也保护一下:

useEffect(() => {
let isMounted = true;
const ws = new WebSocket('ws://example.com');

ws.onmessage = (e) => {
if (isMounted) {
setMessage(e.data);
}
};

return () => {
isMounted = false;
ws.onmessage = null;
ws.close();
};
}, []);


这样内存快照里应该就干净了。
点赞
2026-03-19 05:02