sse连接生命周期必须与可视组件严格对齐:组件挂载时创建,卸载时显式调用.close()关闭;keep-alive场景下应暂停监听而非关闭,仅在彻底销毁时关闭连接,避免幽灵连接和资源泄漏。

SSE 是单向长连接,一旦建立就持续占用服务端资源和客户端连接句柄。在 SPA 中,若用户切换路由、组件被隐藏(如 v-if 为 false 或 React 中 unmount),但 SSE 连接未主动关闭,就会形成“幽灵连接”——组件已不可见,却仍在后台接收并处理数据,既浪费带宽、增加服务端压力,又可能引发内存泄漏或状态错乱。
关键原则:SSE 连接生命周期必须与可视组件生命周期严格对齐。
组件挂载时建立连接,卸载时必须显式关闭
SSE 的 EventSource 实例是真实存在的 JS 对象,它背后维持着一个 HTTP 长连接。不手动调用 .close(),浏览器不会自动释放连接(即使 DOM 元素消失,连接仍存活)。
-
React 中,在
useEffect的清理函数里关闭:useEffect(() => { const es = new EventSource('/api/stream'); es.onmessage = (e) => { /* 处理数据 */ }; return () => { es.close(); // 必须调用,否则连接持续存在 }; }, []); -
Vue 3 中,在
onBeforeUnmount里关闭:onBeforeUnmount(() => { if (eventSource) { eventSource.close(); eventSource = null; } }); 若使用 Keep-Alive 包裹组件(如
<keep-alive></keep-alive>或React.memo+ 状态缓存),注意:Keep-Alive 不等于连接保持。组件被缓存(activated/deactivated)时,视觉上“隐藏”,但 JS 实例仍在内存中——此时应暂停而非关闭连接,避免频繁重连开销。
Keep-Alive 场景下区分“隐藏”与“销毁”
Keep-Alive 组件会触发 activated 和 deactivated 生命周期钩子(Vue)或通过 useEffect 监听 visibility 变化(React)。此时不宜直接 .close(),而应:
- 暂停接收:设置
es.onmessage = null,停止处理新数据; - 暂存连接实例,不销毁
EventSource对象; - 在
activated时恢复监听(重新赋值onmessage); - 仅在真正卸载(如路由彻底离开、用户关闭标签页)时才
.close()。
⚠️ 注意:
EventSource没有原生的“暂停”方法,只能靠逻辑控制是否响应事件。不要依赖es.readyState === 0判断是否可用,它可能处于 reconnecting 状态。
避免重复创建连接导致连接堆积
SPA 路由反复进出、Keep-Alive 缓存失效、或错误地在 render 函数里新建 EventSource,都会造成多个连接并存。
- 确保每个逻辑流只创建一个
EventSource实例; - 使用 Ref 或组件级变量缓存实例(React 的
useRef,Vue 的ref或data); - 创建前先检查是否已有有效连接:
if (es && es.readyState === EventSource.OPEN) return;
结合 AbortController 增强可控性(可选进阶)
虽然 EventSource 不原生支持 AbortController,但可在封装层模拟中断语义:
- 创建连接时生成唯一 token(如
Symbol()或 UUID); - 在关闭前标记该 token 为无效;
-
onmessage回调开头加校验:if (currentToken !== token) return; - 配合
fetch + text/event-stream手动实现时,可直接传入signal,获得更精细的中断能力。
本质上,SSE 在 SPA 中的稳定性不取决于协议本身,而取决于你是否把它当作一个需要手动管理的“外部资源”——就像定时器、Canvas 上下文或 WebSocket 一样,谁创建,谁负责销毁或暂停。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











