共计 3159 个字符,预计需要花费 8 分钟才能阅读完成。
背景与痛点
传统的 HTTP 请求 - 响应模式在对话场景中存在明显不足。当处理类似 ChatGPT 这样的生成式 AI 响应时,一次性等待完整响应会导致两个问题:

- 用户需要长时间等待,体验不佳
- 服务器需要持续计算直到生成完整内容,增加资源消耗
流式输出能够边生成边传输,实现逐字打印效果,大幅提升用户体验。但要在前端实现流畅的流式渲染,需要解决几个技术难点:
- 如何高效处理持续到达的分块数据
- 如何避免频繁 DOM 操作导致的性能问题
- 如何管理连接状态和错误恢复
技术选型
主流流式传输方案有三种:
- SSE(Server-Sent Events)
- 优点:HTTP 协议,简单易用
-
缺点:单向通信,不能从客户端向服务器发送数据
-
WebSocket
- 优点:全双工通信,适合实时交互
-
缺点:需要额外维护连接状态
-
长轮询
- 优点:兼容性好
- 缺点:效率低下,不推荐
对于 ChatGPT 这类需要双向交互的场景,WebSocket 是最佳选择。下面我们重点介绍 WebSocket 方案。
核心实现
响应式数据管理
Vue3 的 Composition API 非常适合管理流式数据状态。我们使用 ref 来存储消息内容:
import {ref} from 'vue'
const messages = ref<Array<{role: string, content: string}>>([])
const currentMessage = ref('')
WebSocket 连接管理
创建 WebSocket 连接并处理消息:
const socket = new WebSocket('wss://your-api-endpoint')
socket.onmessage = (event) => {const data = JSON.parse(event.data)
if (data.type === 'partial') {currentMessage.value += data.content} else if (data.type === 'complete') {
messages.value.push({
role: 'assistant',
content: currentMessage.value
})
currentMessage.value = ''
}
}
完整组件示例
<template>
<div class="chat-container">
<div v-for="(msg, index) in messages" :key="index">
<div :class="['message', msg.role]">
{{msg.content}}
</div>
</div>
<div v-if="currentMessage" class="message assistant">
{{currentMessage}}
</div>
</div>
</template>
<script setup lang="ts">
import {ref, onUnmounted} from 'vue'
interface ChatMessage {
role: 'user' | 'assistant'
content: string
}
const messages = ref<ChatMessage[]>([])
const currentMessage = ref('')
let socket: WebSocket | null = null
const connectWebSocket = () => {socket = new WebSocket('wss://your-api-endpoint')
socket.onopen = () => {console.log('WebSocket connected')
}
socket.onmessage = (event) => {const data = JSON.parse(event.data)
if (data.type === 'partial') {currentMessage.value += data.content} else if (data.type === 'complete') {
messages.value.push({
role: 'assistant',
content: currentMessage.value
})
currentMessage.value = ''
}
}
socket.onclose = () => {console.log('WebSocket disconnected')
// 可以在这里添加重连逻辑
}
}
const sendMessage = (content: string) => {messages.value.push({ role: 'user', content})
socket?.send(JSON.stringify({ content}))
}
onUnmounted(() => {socket?.close()
})
connectWebSocket()
</script>
性能优化
虚拟滚动
长对话列表会导致性能问题,可以使用 vue-virtual-scroller 实现虚拟滚动:
import {RecycleScroller} from 'vue-virtual-scroller'
// 在模板中替换 v -for 部分
<RecycleScroller
class="scroller"
:items="messages"
:item-size="50"
key-field="id"
>
<template v-slot="{item}">
<div :class="['message', item.role]">
{{item.content}}
</div>
</template>
</RecycleScroller>
渲染节流
对于频繁更新的 currentMessage,可以使用 watch 配合 throttle 减少渲染压力:
import {throttle} from 'lodash-es'
const throttledUpdate = throttle((val: string) => {// 这里可以添加一些 DOM 操作优化}, 100)
watch(currentMessage, throttledUpdate)
生产环境注意事项
连接稳定性
- 实现自动重连机制
- 添加心跳检测
- 网络状态变化时重新连接
const reconnectInterval = ref<NodeJS.Timeout>()
const setupReconnect = () => {reconnectInterval.value = setInterval(() => {if (socket?.readyState === WebSocket.CLOSED) {connectWebSocket()
}
}, 5000)
}
onUnmounted(() => {clearInterval(reconnectInterval.value)
})
错误处理
- 添加错误边界处理
- 实现优雅降级
- 记录错误日志
socket.onerror = (error) => {console.error('WebSocket error:', error)
// 可以在这里显示用户友好的错误提示
}
内存泄漏预防
- 组件卸载时清理所有订阅
- 取消未完成的请求
- 清理定时器
onUnmounted(() => {socket?.close()
clearInterval(reconnectInterval.value)
throttledUpdate.cancel()})
总结与延伸
本文介绍的流式输出方案不仅适用于 ChatGPT,还可以应用于:
- 实时日志展示
- 金融数据推送
- 在线协作编辑
关键点总结:
- 使用 WebSocket 实现双向流式通信
- 利用 Vue3 响应式系统高效管理状态
- 通过虚拟滚动和节流优化性能
- 完善的错误处理和连接管理
未来可以考虑的优化方向:
- 支持断线续传
- 添加消息压缩
- 实现多标签页同步
希望这篇文章能帮助你在 Vue3 项目中实现流畅的流式输出体验。如果有任何问题或建议,欢迎交流讨论。
正文完
