sse 两种调用方式

✅ 方式一:new EventSource()(浏览器原生)

一、适用场景

✅ 简单 SSE

✅ GET 请求

✅ 无需 Header / Token

✅ 日志 / 通知 / 监控


二、SSE 消息类型

ts 复制代码
// types/sse.ts
export interface SSEMessage<T = any> {
  event: string
  data: T
}

三、EventSource Hook(推荐)

composables/useEventSource.ts

ts 复制代码
import { ref, onMounted, onUnmounted } from 'vue'

export function useEventSource<T = any>(
  url: string,
  options?: {
    event?: string
  }
) {
  const data = ref<T[]>([])
  const connected = ref(false)
  const error = ref<Event | null>(null)

  let es: EventSource | null = null

  const connect = () => {
    es = new EventSource(url)

    es.onopen = () => {
      connected.value = true
    }

    es.onerror = (err) => {
      error.value = err
      connected.value = false
      es?.close()
    }

    if (options?.event) {
      es.addEventListener(options.event, (e: MessageEvent<string>) => {
        try {
          data.value.push(JSON.parse(e.data))
        } catch {
          data.value.push(e.data as any)
        }
      })
    } else {
      es.onmessage = (e: MessageEvent<string>) => {
        try {
          data.value.push(JSON.parse(e.data))
        } catch {
          data.value.push(e.data as any)
        }
      }
    }
  }

  onMounted(connect)
  onUnmounted(() => es?.close())

  return {
    data,
    connected,
    error,
    close: () => es?.close()
  }
}

四、组件中使用

vue 复制代码
<script setup lang="ts">
import { useEventSource } from '@/composables/useEventSource'

const { data, connected } = useEventSource<{ msg: string }>(
  '/api/sse',
  { event: 'message' }
)
</script>

<template>
  <p>状态:{{ connected ? '已连接' : '断开' }}</p>
  <ul>
    <li v-for="(item, i) in data" :key="i">
      {{ item.msg }}
    </li>
  </ul>
</template>

✅ 方式二:Axios + Fetch Stream(企业级)

一、适用场景

✅ 大模型对话

✅ 需要 Token

✅ POST

✅ 生产环境


二、Axios SSE 请求封装

services/sseAxios.ts

ts 复制代码
import axios from 'axios'

export interface SSEMessage<T = any> {
  event: string
  data: T
}

export async function requestSSE<T = any>(
  url: string,
  onMessage: (msg: SSEMessage<T>) => void,
  options?: {
    method?: 'GET' | 'POST'
    data?: any
    headers?: Record<string, string>
  }
) {
  const response = await axios({
    url,
    method: options?.method ?? 'GET',
    data: options?.data,
    headers: {
      Accept: 'text/event-stream',
      'Cache-Control': 'no-cache',
      ...options?.headers
    },
    responseType: 'stream',
    adapter: 'fetch'
  })

  const reader = response.data.getReader()
  const decoder = new TextDecoder('utf-8')
  let buffer = ''

  while (true) {
    const { done, value } = await reader.read()
    if (done) break

    buffer += decoder.decode(value, { stream: true })

    const parts = buffer.split('\n\n')
    buffer = parts.pop() || ''

    for (const part of parts) {
      const lines = part.split('\n')
      let event = 'message'
      let data = ''

      for (const line of lines) {
        if (line.startsWith('event:'))
          event = line.replace('event:', '').trim()
        if (line.startsWith('data:'))
          data = line.replace('data:', '').trim()
      }

      if (data) {
        try {
          onMessage({ event, data: JSON.parse(data) })
        } catch {
          onMessage({ event, data: data as any })
        }
      }
    }
  }
}

三、Axios SSE Hook

composables/useSSEAxios.ts

ts 复制代码
import { ref } from 'vue'
import { requestSSE } from '@/services/sseAxios'

export function useSSEAxios<T = any>(url: string) {
  const data = ref<T[]>([])
  const loading = ref(false)
  const error = ref<Error | null>(null)

  const start = (payload?: any) => {
    loading.value = true
    error.value = null

    requestSSE<T>(
      url,
      (msg) => {
        if (msg.event === 'message') {
          data.value.push(msg.data)
        }
      },
      {
        method: 'POST',
        data: payload
      }
    ).catch(err => {
      error.value = err
    }).finally(() => {
      loading.value = false
    })
  }

  return { data, loading, error, start }
}

四、组件中使用(大模型对话)

vue 复制代码
<script setup lang="ts">
import { useSSEAxios } from '@/composables/useSSEAxios'

const { data, loading, start } = useSSEAxios<{
  role: string
  content: string
}>('/api/chat')

const send = () => {
  start({
    messages: [{ role: 'user', content: '你好' }]
  })
}
</script>

<template>
  <button @click="send" :disabled="loading">
    {{ loading ? '生成中...' : '发送' }}
  </button>

  <div v-for="(msg, i) in data" :key="i">
    {{ msg.content }}
  </div>
</template>

五、最终选择建议(记住这张表)

场景 选谁
简单通知 / 日志 ✅ EventSource
大模型 / AI ✅ Axios
需要 Token ✅ Axios
想省事 ✅ EventSource
公司项目 ✅ Axios

相关推荐
Csvn6 小时前
OpenSpec 详细使用教程
前端
之歆7 小时前
Day19_LESS 完全指南——从入门到工程实践
前端·css·less
云水一下8 小时前
HTML5 从入门到精通:实战收官——从零搭建完整静态网站,综合运用所有知识
前端·html5
不总是8 小时前
Windows 系统 Node.js 免安装版(zip)安装与配置教程(2026 最新)
前端·windows·node.js
冬奇Lab8 小时前
每日一个开源项目(第105篇):Twenty - 跳出 Salesforce 的圈套,定义现代开源 CRM
前端·后端·开源
zhangyao9403309 小时前
开发pc端时,表格的高度怎么设置才能铺满页面
前端·javascript·elementui
XinZong9 小时前
实测OpenClaw虾淘:全民工具AI时代,冷门非工具类的Skill还能出圈吗?
javascript
kjs--9 小时前
浏览器书签执行脚本
前端
烛衔溟9 小时前
TypeScript 类的类型 —— 作为类型使用
javascript·ubuntu·typescript
之歆9 小时前
Day16_JavaScript 轮播图与事件工程实战(下篇)
服务器·开发语言·前端·javascript·网络·性能优化