一、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前端网发表,如需转载,请注明页面地址。
code前端网



发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。