前言:为什么需要流式输出?
想象你正在和AI助手对话,每次提问后都要等待几秒才能看到完整回答——这种体验就像看没有缓冲的网络视频。而流式输出(Stream Output) 技术,能让AI的回答像短视频一样"边播边看",用户看到首个字符的响应时间从2秒缩短到0.5秒,交互体验发生质的飞跃。
📡 流式输出技术解析
1. 传统输出 vs 流式输出
| 特性 | 传统输出 | 流式输出 |
|---|---|---|
| 数据传输方式 | 一次性返回完整JSON | 分块传输二进制流 |
| 用户感知 | 长时间等待→突然显示全文 | 实时逐字显示 |
| 典型场景 | 搜索结果加载 | 智能客服对话、代码补全 |
2. 核心技术原理
流式输出的本质是二进制分块传输:
- 服务器将回答拆分成多个小数据包(Chunk)
- 客户端通过
ReadableStream逐个接收 - 使用
TextDecoder将二进制转换为字符串 - 通过正则解析每个数据块中的有效内容
React流式输出实战:代码解析
我们以DeepSeek API调用为例,拆解关键实现步骤:
1. 状态管理设计
const [question, setQuestion] = useState(""); // 用户输入
const [content, setContnet] = useState(""); // 显示内容
const [c, setC] = useState(false); // 流式开关
2. 核心请求函数
const update = async () => {
if (!question) return;
setContnet("加载中...");
const response = await fetch(API_URL, {
method: "POST",
headers: { /* 认证头 */ },
body: JSON.stringify({
model: "deepseek-chat",
messages: [{ role: "user", content: question }],
stream: c // 关键:开启流式传输
})
});
// 流式处理逻辑
if (c) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (!done) {
const { value, done } = await reader.read();
const chunkValue = buffer + decoder.decode(value);
// 关键解析逻辑
processStreamData(chunkValue);
}
}
};
3. 流式数据处理函数
const processStreamData = (chunkValue) => {
let str = '';
const lines = chunkValue.split("\n").filter(line => line.startsWith("data:"));
for (const line of lines) {
const incoming = line.slice(6);
if (incoming === "[DONE]") break;
try {
const data = JSON.parse(incoming);
const delta = data.choices[0].delta.content;
if (delta) {
str += delta;
setContnet(str); // 实时更新状态
}
} catch (err) {
console.error("解析错误:", err);
}
}
};
关键技术点详解
1. 二进制流解码
javascript
const decoder = new TextDecoder('utf-8', { ignoreBOM: true });
// 将Uint8Array转换为字符串,处理中文乱码问题
2. 分块数据拼接
let buffer = ""; // 缓存不完整的数据块
// 示例:服务器返回"hello{"con"..."
buffer += "hello"; // 首次接收
buffer += "{"cont..."; // 第二次接收
// 最终拼接成完整JSON:hello{"content":"..."}
3. 终止信号检测
if (incoming === "[DONE]") {
done = true;
setContnet(prev => prev + "\n\n对话结束");
}
结语:重新定义AI交互
流式输出技术让AI对话从"回合制"进化为"实时交互",就像从写信到即时聊天的革命。通过本文的实战解析,你已掌握在React中实现流式输出的核心技术。下次当用户看到AI的回答"逐字蹦出"时,背后正是你今天学习的技术在支撑这种魔法般的体验。
完整代码:
import { useState } from "react";
import "./index.css";
function DeepSeek() {
let str=''
const [question, setQuestion] = useState("");
const [content, setContnet] = useState("");
const [c, setC] = useState(false);
const update = async () => {
// 获取到用户在输入框中输入的内容
if (!question) return;
setContnet("加载中..."); //跟deepseek交互
const ednpoint = "https://api.deepseek.com/chat/completions ";
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${import.meta.env.VITE_DEEPSEEK_API_KEY}`,
};
const response = await fetch(ednpoint, {
method: "POST",
headers: headers,
body: JSON.stringify({
model: "deepseek-chat",
messages: [{ role: "user", content: question }],
// user system ai三种系统角色 以数组的形式传递
stream: c,
}),
});
// 流式输出
if (c) {
const reader = response.body.getReader(); //读取器
// 利用getReader()方法获取可读流
const decoder = new TextDecoder(); //解码器 解码二进制的
let done = false; //是否解码完成 解码长度可能为1000 ,时间久
let buffer = "";
while (!done) {
const { value, done } = await reader.read(); //读取到二进制数据 迭代对象
//两个值 value表示此刻读到的值 done表示是否解码完成
const chunkValue = buffer + decoder.decode(value); //读到的一节的二进制数据 转换为字符串
buffer = "";
const lines = chunkValue
.split("\n")
.filter((line) => line.startsWith("data:"));
// console.log(chunkValue);
for (const line of lines) {
const incoming = line.slice(6);
if (incoming === "[DONE]") {
done = true;
break;
}
try {
const data = JSON.parse(incoming);
const delta = data.choices[0].delta.content; //读取到大模型返回数据中的一小节中文 每次while只能返回一小节二进制
if (delta) {
str+= delta;
setContnet(str);
// setContnet((prev)=>prev+=delta);
}
} catch (err) {
console.log(err);
}
}
// 格式化数据
}
// console.log(response);
} else {
const data = await response.json();
console.log(data.choices[0].message.content);
// json格式化返回的二进制
//以上为非流式输出
}
};
return (
<div className="container">
<label>请输入</label>
<div>
<input
type="text"
className="input"
onChange={(e) => {
setQuestion(e.target.value);
}}
/>
<button onClick={update}>发送</button>
</div>
<div className="response">
<div>
<label>streaming</label>
<input
type="checkbox"
onChange={(e) => {
setC(e.target.checked);
}}
/>
</div>
<div>{content}</div>
</div>
</div>
);
}
export default DeepSeek;