前言
在 AI 问答、实时日志、消息推送、大文本展示等场景中,传统一次性请求等待时间长、体验差,而流式输出可以实现 "收到一段、渲染一段",显著降低首屏等待时间、提升流畅度。
本文不讲虚、不堆砌概念,用最通俗的语言和可直接运行的代码,把前端流式输出从原理到实践讲透。
一、流式输出核心原理
1.1 什么是流式输出?
流式输出(Streaming)是指服务端不以完整数据包返回,而是将内容拆分成多个小块(Chunk),持续推送到前端;浏览器收到一块就解析一块,实现渐进式渲染。
与传统请求的区别:
- 传统请求:等待全部数据返回 → 一次性渲染
- 流式输出:边接收、边解析、边渲染
1.2 优势
- 首屏展示更快,无需等待全量返回
- 内存占用更低,不缓存超大文本
- 用户体验更流畅(打字机效果)
- 适合超长文本、实时日志、AI 回复
1.3 关键技术支撑
- HTTP/1.1 Chunked Transfer Encoding(分块传输)
- Fetch + ReadableStream
- TextDecoder 字符解码
- SSE(Server-Sent Events)
- WebSocket(双向实时通信)
二、原生 JavaScript 实现流式输出
2.1 Fetch + ReadableStream 标准实现
这是目前最通用、最适合 AI 打字机的方案。
async function readStream(url, callback) {
const response = await fetch(url)
const reader = response.body.getReader()
const decoder = new TextDecoder('utf-8', { stream: true })
while (true) {
const { done, value } = await reader.read()
if (done) break
// 解码并返回片段
const chunk = decoder.decode(value)
callback(chunk)
}
}
使用示例
const output = document.querySelector('#output')
readStream('/api/stream', (text) => {
output.innerHTML += text
output.scrollTop = output.scrollHeight
})
关键点说明:
response.body.getReader()获取流读取器TextDecoder用于将二进制流转为字符串{ stream: true }避免中文截断乱码- 循环
read()直到done = true
2.2 SSE 方式实现(服务端推送)
适合简单单向推送,使用浏览器原生 EventSource。
const eventSource = new EventSource('/api/sse')
eventSource.onmessage = (event) => {
const text = event.data
output.innerHTML += text
}
eventSource.onerror = () => {
eventSource.close()
}
优点:开箱即用、自动重连。缺点:只支持 GET、无法自定义请求头。
三、Vue 3 项目中实现流式输出
以下是生产环境可直接使用的封装。
<template>
<div class="stream-content" ref="contentRef">{{ result }}</div>
</template>
<script setup>
import { ref, onUnmounted } from 'vue'
const result = ref('')
const contentRef = ref(null)
let abortController = null
async function startStream() {
abortController = new AbortController()
result.value = ''
try {
const response = await fetch('/api/stream', {
signal: abortController.signal
})
const reader = response.body.getReader()
const decoder = new TextDecoder('utf-8', { stream: true })
while (true) {
const { done, value } = await reader.read()
if (done) break
result.value += decoder.decode(value)
// 自动滚动到底部
nextTick(() => {
if (contentRef.value) {
contentRef.value.scrollTop = contentRef.value.scrollHeight
}
})
}
} catch (err) {
console.error('流读取异常', err)
}
}
// 组件卸载时停止流
onUnmounted(() => {
abortController?.abort()
})
startStream()
</script>
四、React 项目中实现流式输出
import { useState, useEffect, useRef } from 'react'
export default function Stream() {
const [text, setText] = useState('')
const ref = useRef(null)
const controllerRef = useRef(null)
useEffect(() => {
async function read() {
const controller = new AbortController()
controllerRef.current = controller
const res = await fetch('/api/stream', { signal: controller.signal })
const reader = res.body.getReader()
const decoder = new TextDecoder('utf-8', { stream: true })
while (true) {
const { done, value } = await reader.read()
if (done) break
const chunk = decoder.decode(value)
setText((prev) => prev + chunk)
if (ref.current) {
ref.current.scrollTop = ref.current.scrollHeight
}
}
}
read()
return () => {
controllerRef.current?.abort()
}
}, [])
return <div ref={ref} style={{ height: 400, overflow: 'auto' }}>{text}</div>
}
五、性能优化与工程化实践
5.1 避免高频渲染卡顿
流数据推送过快会导致页面卡顿,可使用缓冲合并。
let buffer = []
let timer = null
function pushChunk(chunk) {
buffer.push(chunk)
if (!timer) {
timer = setTimeout(() => {
callback(buffer.join(''))
buffer = []
timer = null
}, 30)
}
}
5.2 XSS 防护
流式内容必须转义,防止恶意脚本执行。
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
5.3 取消流与防止内存泄漏
使用 AbortController 取消请求,组件卸载时必须中止。
const controller = new AbortController()
fetch(url, { signal: controller.signal })
// 取消
controller.abort()
六、常见问题与排查
6.1 流不生效
- 服务端未返回
Transfer-Encoding: chunked - Nginx 未配置
proxy_buffering off; - 服务端未保持长连接
6.2 中文乱码
必须设置:
const decoder = new TextDecoder('utf-8', { stream: true })
6.3 页面卡顿
- 未做渲染节流
- 未做自动滚动优化
- 内容过大未做虚拟滚动
6.4 调试方法
- Chrome F12 → Network → Response 查看分块
- 使用
curl -N http://xxx测试服务端流是否正常 - 查看是否触发 cors / 403 / 404 等错误
七、总结
前端流式输出是现代前端必备技能,尤其在 AI 交互、实时消息、日志展示等场景必不可少。
核心三件套:
- Fetch + ReadableStream 读取流
- TextDecoder 解码
- 逐段渲染 + 防抖 + 安全转义
掌握本文内容,你可以:
- 实现 ChatGPT 式打字机效果
- 完成实时日志、监控大屏
- 优化大文本加载速度
- 写出生产可用的流式组件