让AI对话实时可见:React流式输出实战指南

580 阅读4分钟

前言:为什么需要流式输出?

想象你正在和AI助手对话,每次提问后都要等待几秒才能看到完整回答——这种体验就像看没有缓冲的网络视频。而流式输出(Stream Output) 技术,能让AI的回答像短视频一样"边播边看",用户看到首个字符的响应时间从2秒缩短到0.5秒,交互体验发生质的飞跃。

📡 流式输出技术解析

1. 传统输出 vs 流式输出

特性传统输出流式输出
数据传输方式一次性返回完整JSON分块传输二进制流
用户感知长时间等待→突然显示全文实时逐字显示
典型场景搜索结果加载智能客服对话、代码补全

2. 核心技术原理

流式输出的本质是二进制分块传输

  1. 服务器将回答拆分成多个小数据包(Chunk)
  2. 客户端通过ReadableStream逐个接收
  3. 使用TextDecoder将二进制转换为字符串
  4. 通过正则解析每个数据块中的有效内容

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的回答"逐字蹦出"时,背后正是你今天学习的技术在支撑这种魔法般的体验。

%E5%B1%8F%E5%B9%95%E5%BD%95%E5%88%B6%202025-07-15%20232833_converted.gif

完整代码:

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;