一、SSE 是啥?和 Vue3 结合能解决啥问题?
做前端项目时,要是碰到“服务端主动给前端推数据”的需求,比如实时通知、任务进度提醒这些场景,Server - Sent Events(SSE)是个轻量又好用的方案,那在Vue3项目里咋把SSE用起来?实际开发要避哪些坑?和WebSocket选哪个更合适?今天就围绕Vue3 + SSE展开聊聊,把技术细节和实战经验掰碎了讲。
Server - Sent Events(SSE)是HTML5推出的Web API,基于HTTP长连接,让服务端能主动给客户端发实时数据,和传统轮询(定时发请求查更新)相比,它不用频繁建立连接,服务端有数据就推送,效率高还省资源。
Vue3和SSE结合有啥价值?Vue3的响应式(ref/reactive)、组件化特性,能让SSE收到的数据快速更新UI,打个比方,做个实时通知组件,SSE收到新消息后,用reactive存储消息列表,组件就能自动渲染变化,开发体验顺畅,用户也能实时看到更新。
举个简单场景:后台有文章审核系统,审核状态变化时(通过/驳回),服务端用SSE推送状态,前端Vue3组件实时更新审核状态提示,用户不用刷新页面就能知晓结果。
Vue3 项目里咋搭建 SSE 连接?(从服务端到前端代码)
搭建SSE连接分服务端准备、前端Vue3组件实现两步来讲。
(一)服务端咋发 SSE 数据?(以Node.js + Express为例)
SSE要求服务端响应头必须设置 Content-Type: text/event-stream
,而且数据格式得是data: 内容\n\n
这种形式(每行以data:
开头,空行分隔不同消息),看下面代码示例:
const express = require('express'); const app = express(); app.get('/sse', (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); // 模拟定时发数据(实际可能是数据库变更、队列消息触发) const timer = setInterval(() => { const data = { message: '新通知', time: new Date().toLocaleTimeString() }; res.write(`data: ${JSON.stringify(data)}\n\n`); // 必须严格格式 }, 3000); // 客户端断开时清除定时器 req.on('close', () => { clearInterval(timer); res.end(); }); }); app.listen(3000, () => console.log('服务端启动在3000端口'));
这里得注意:要处理CORS跨域,比如添加 res.setHeader('Access-Control-Allow-Origin', '*');
(生产环境要限制具体域名);数据格式错了前端收不到,必须保证data:
前缀和空行的格式要求。
(二)Vue3 前端咋收 SSE 数据?
在Vue3组件里用EventSource
API,它是浏览器原生支持SSE的对象,步骤如下:
- 组件创建时初始化EventSource连接;
- 监听
onmessage
事件,把数据存到响应式变量; - 组件销毁时关闭连接,避免内存泄漏。
看代码示例(Composition API):
<template> <div> <p v-for="item in messages" :key="item.id">{{ item.message }} - {{ item.time }}</p> </div> </template> <script setup> import { ref, onMounted, onUnmounted } from 'vue'; const messages = ref([]); // 存SSE收到的消息 let eventSource = null; onMounted(() => { // 连接服务端SSE接口 eventSource = new EventSource('http://localhost:3000/sse'); // 监听消息 eventSource.onmessage = (event) => { const data = JSON.parse(event.data); // 往响应式数组里加数据,触发UI更新 messages.value.push({ ...data, id: Date.now() }); }; // 监听错误(比如连接断了) eventSource.onerror = (err) => { console.error('SSE连接出错:', err); // 这里可以加重连逻辑,后面讲坑的时候详细说 eventSource.close(); // 先关旧连接 }; }); onUnmounted(() => { if (eventSource) { eventSource.close(); // 组件销毁时关闭连接 } }); </script>
这段代码里,EventSource
初始化后自动保持长连接,服务端发数据就触发onmessage
,Vue3的ref
让messages
变化时,模板里的列表自动更新,实现实时效果。
实际项目用 SSE 容易踩哪些坑?咋解决?
开发时碰到的问题不少,挑几个典型的讲讲:
(一)连接中断后咋自动重连?
SSE本身有retry机制,但服务端重启、网络波动会导致连接断开,前端要自己处理重连:
思路是监听onerror
事件,延迟一段时间后重新创建EventSource
,看优化后的前端代码片段:
let eventSource = null; let retryTimer = null; const reconnect = () => { if (retryTimer) clearTimeout(retryTimer); retryTimer = setTimeout(() => { eventSource = new EventSource('http://localhost:3000/sse'); // 重新绑定onmessage和onerror eventSource.onmessage = handleMessage; eventSource.onerror = handleError; }, 5000); // 5秒后重连 }; const handleError = (err) => { console.error('SSE出错:', err); eventSource.close(); reconnect(); // 触发重连 }; onMounted(() => { eventSource = new EventSource('http://localhost:3000/sse'); eventSource.onmessage = handleMessage; eventSource.onerror = handleError; });
服务端也可以在响应里加retry: 时间毫秒数
告诉前端重连间隔,比如res.write('retry: 3000\n');
,但前端自己控制重连更灵活。
(二)跨域问题咋处理?
如果前端和服务端域名不同,服务端必须配置CORS,以Express为例,安装cors
中间件:
const cors = require('cors'); app.use(cors({ origin: 'http://前端域名', // 生产环境别用*,限制具体域名 methods: ['GET'], // SSE只用GET请求 allowedHeaders: ['Content-Type'] }));
前端EventSource
连接时,浏览器会自动发OPTIONS请求,服务端处理好CORS响应头才能建立连接。
(三)消息格式不对,前端收不到数据?
服务端必须严格遵循SSE格式:每行以data:
开头,多个消息用\n\n
分隔,比如服务端如果写成res.write(JSON.stringify(data))
,前端完全收不到,因为少了data:
前缀。
消息里如果有换行,要转义或者分成多个data:
行,比如大段文本可以拆:
data: 第一行内容
data: 第二行内容
\n\n
前端event.data
会自动把多行data:
合并成一个字符串,所以服务端要保证格式正确。
(四)组件销毁后,EventSource 没关闭导致内存泄漏?
Vue3的onUnmounted
钩子要记得调用eventSource.close()
,如果多个组件用SSE,没关闭的话,浏览器里会残留很多长连接,占资源还可能触发重复请求。
Vue3 + SSE 能玩出哪些实时场景?(举几个实战例子)
技术落地得看场景,这些案例能帮你打开思路:
(一)后台任务进度跟踪(比如大文件上传、数据导出)
场景:用户触发一个耗时操作(比如导出10万条数据),服务端处理时,用SSE每隔一段时间发进度(比如30%、70%、100%)。
前端Vue3实现:
- 用
ref
存储进度值(0 - 100); - 做个进度条组件,绑定进度值;
- SSE收到
{ progress: 50 }
时,更新进度条。
服务端逻辑:任务开始后,定时计算进度(比如读文件行数、数据库查询进度),通过SSE推给前端。
(二)实时通知中心(系统提醒、聊天消息)
场景:用户登录后,右上角有个通知铃铛,新消息(比如有人@你、评论你的文章)由服务端主动推送。
前端Vue3实现:
- 用
reactive
存储通知列表,包含消息内容、时间、未读状态; - 点击通知标记为已读,调用接口更新服务端状态;
- SSE收到新通知时,往列表里加数据,铃铛显示未读数量(用计算属性统计)。
服务端触发条件:当消息表新增记录时,触发SSE推送(可以用数据库触发器、消息队列消费者来触发)。
(三)数据仪表盘(实时监控、股票行情)
场景:监控系统里,CPU使用率、请求QPS等指标每秒更新;股票App里,股价实时变动。
前端Vue3实现:
- 用ECharts或VueChart组件,绑定响应式的数据源;
- SSE每秒收到新的指标数据,更新数据源,图表自动重绘。
服务端数据来源:从监控系统、金融接口实时拉取数据,再通过SSE广播给所有连接的前端。
SSE 和 WebSocket 咋选?Vue3 项目里咋权衡?
很多人纠结这俩技术,得看场景:
(一)核心区别是啥?
- 协议层面:SSE基于HTTP,WebSocket是独立的WS协议;
- 通信方向:SSE只能服务端→客户端(单向),WebSocket是全双工(双向);
- 兼容性:SSE是HTML5 API,现代浏览器都支持;WebSocket也一样,但旧环境(比如一些嵌入式设备)可能有限;
- 复杂度:SSE开发更简单,只用处理服务端推;WebSocket要处理连接、心跳、消息解析等,代码量更大。
(二)Vue3 项目里咋选?
- 选SSE的情况:主要需求是“服务端主动发,前端只收”,比如通知、进度、行情,开发快,HTTP生态兼容好(比如Nginx反向代理SSE很方便)。
- 选WebSocket的情况:需要前端也能主动给服务端发消息(比如聊天里的“发送”按钮、实时协作的操作同步),必须双向通信时用。
举个例子:做“实时聊天”功能,用户发消息(前端→服务端)和收消息(服务端→前端)都需要,这时候WebSocket更合适;做“系统公告自动推送”,只有服务端推,用SSE足够。
Vue3 里优化 SSE 体验的小技巧
除了基础用法,这些技巧能让代码更优雅、性能更好:
(一)用自定义指令封装 EventSource,减少重复代码
Vue3的自定义指令可以封装SSE的创建、销毁逻辑。
// directives/sse.js export const vSse = { mounted(el, binding) { const { url, onMessage, onError } = binding.value; const eventSource = new EventSource(url); eventSource.onmessage = onMessage; eventSource.onerror = onError; el.__eventSource__ = eventSource; // 存到元素上,方便销毁 }, unmounted(el) { if (el.__eventSource__) { el.__eventSource__.close(); } } };
组件里使用:
<template> <div v-sse="{ url: 'http://localhost:3000/sse', onMessage: handleMessage, onError: handleError }"></div> </template>
这样多个组件用SSE时,不用重复写onMounted
和onUnmounted
的逻辑。
(二)结合 Pinia/Vuex 做全局状态管理
如果多个组件需要订阅同一份SSE数据(比如全局通知),把SSE的消息处理逻辑放到Pinia里:
// stores/notification.js import { defineStore } from 'pinia'; export const useNotificationStore = defineStore('notification', { state: () => ({ messages: [] }), actions: { initSSE() { this.eventSource = new EventSource('/sse'); this.eventSource.onmessage = (event) => { this.messages.push(JSON.parse(event.data)); }; }, closeSSE() { if (this.eventSource) { this.eventSource.close(); } } } });
组件里只用调用initSSE()
和 closeSSE()
,数据存在Pinia里,多个组件共享更方便。
(三)用 Suspense 处理加载状态
Vue3的Suspense可以在SSE连接建立前,显示加载动画。
<template> <Suspense> <template #default> <RealTimeComponent /> </template> <template #fallback> <div>正在建立实时连接...</div> </template> </Suspense> </template> <script setup> import { onMounted } from 'vue'; import RealTimeComponent from './RealTimeComponent.vue'; // 模拟异步初始化SSE(实际可以在onMounted里延迟执行) const loadSSE = () => new Promise((resolve) => { setTimeout(() => { resolve(); }, 1000); }); onMounted(async () => { await loadSSE(); }); </script>
这样用户打开页面时,先看到加载提示,SSE连接好后再显示实时内容,体验更友好。
Vue3 + SSE 是实时场景的高效选择
SSE适合“服务端推数据,前端只消费”的场景,和Vue3的响应式、组件化结合,能快速实现实时UI,开发时要注意连接重连、跨域、格式这些坑,根据场景选SSE还是WebSocket,用自定义指令、状态管理库这些技巧,能让代码更简洁易维护。
实际项目里,从小场景(比如通知、进度条)入手练手,再拓展到复杂的仪表盘、实时监控,你会发现SSE比轮询省心太多,还能省服务器资源,要是你在Next.js项目里用,思路也差不多,因为核心是浏览器的EventSource和服务端的响应格式,框架只是载体~
最后提醒下,生产环境要做好SSE连接的监控(比如记录连接数、断开原因),结合日志系统排查问题,这样实时功能才能稳定运行~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。