Code前端首页关于Code前端联系我们

一、SSE 是啥?和 Vue3 结合能解决啥问题?

terry 2小时前 阅读数 3 #Vue
文章标签 SSEVue3

做前端项目时,要是碰到“服务端主动给前端推数据”的需求,比如实时通知、任务进度提醒这些场景,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的对象,步骤如下:

  1. 组件创建时初始化EventSource连接;
  2. 监听onmessage事件,把数据存到响应式变量;
  3. 组件销毁时关闭连接,避免内存泄漏。

看代码示例(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的refmessages变化时,模板里的列表自动更新,实现实时效果。

实际项目用 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时,不用重复写onMountedonUnmounted的逻辑。

(二)结合 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前端网发表,如需转载,请注明页面地址。

发表评论:

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

热门