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

相关推荐
不会敲代码17 小时前
手写 Mini React:从 JSX 到虚拟 DOM 再到 render,搞懂 React 底层原理
前端·javascript·react.js
kyriewen8 小时前
你的代码仓库变成“毛线团”了?Monorepo 用 Turborepo 拆成“乐高积木”
前端·javascript·面试
身如柳絮随风扬8 小时前
你知道什么是 Ajax 吗?—— 从入门到原理,一篇彻底搞懂
前端·ajax·okhttp
旷世奇才李先生9 小时前
Vue3\+TypeScript 2026实战——企业级前端项目架构搭建与性能优化全指南
前端·架构·typescript
Beginner x_u9 小时前
前端八股整理(工程化 02)|CommonJS/ESM、Webpack Loader/Plugin 与Vite 对比
前端·webpack·node.js·plugin·loader
openKaka_9 小时前
createRoot 到底创建了什么:FiberRootNode 和 HostRootFiber 的初始化过程
前端·javascript·react.js
习明然10 小时前
UniApp开发体验感受总结
前端·uni-app
刀法如飞11 小时前
Claude Code Skills 推荐:2026年最值得安装的10个AI技能
前端·后端·ai编程
阿豪只会阿巴11 小时前
【没事学点啥】TurboBlog轻量级个人博客项目——项目介绍
javascript·python·django·html