我第一次在 React 项目中使用 AI 流式输出时,被各种专业术语搞得头晕眼花。折腾了整整两天才搞明白 SSE、ReadableStream 这些东西是怎么回事。今天我把这些经验整理成一篇面向纯小白的教程,保证你跟着做就能跑通。

本文使用 HolySheep AI 作为演示平台,国内直连延迟小于 50ms,汇率相当于 ¥1=$1,比官方渠道节省超过 85% 的成本,非常适合个人开发者和小型项目。

什么是流式输出?

普通 AI 回复是这样的:等 AI 把整段话生成完毕,才一次性显示给你。用户会感觉"卡了好久突然出来了"。

流式输出是这样的:AI 生成第一个字就立刻显示给你,后面一个字一个字地追加显示,就像打字机效果一样。用户体验流畅很多。

大多数主流 AI API 都支持流式输出,HolySheep AI 也不例外,支持 GPT-4.1、Claude Sonnet、Gemini 2.5 Flash 等模型的流式调用。

前置准备

创建 React 项目

打开终端(Windows 用户按 Win+R 输入 cmd,Mac 用户打开终端应用),依次执行以下命令:

npx create-react-app ai-stream-demo
cd ai-stream-demo
npm install

项目创建完成后,你会看到一个标准的 React 项目结构。

安装请求依赖

我们需要一个能发送流式请求的 HTTP 客户端。推荐使用 axios,它对新手友好,文档清晰。

npm install axios

编写流式输出组件

src 文件夹下新建一个文件叫 ChatStream.js,把下面的代码完整复制进去:

import React, { useState } from 'react';
import axios from 'axios';

// HolySheep API 配置
const API_BASE_URL = 'https://api.holysheep.ai/v1';
const API_KEY = 'YOUR_HOLYSHEEP_API_KEY'; // 替换成你的真实 Key

function ChatStream() {
  const [input, setInput] = useState('');
  const [messages, setMessages] = useState([]);
  const [loading, setLoading] = useState(false);

  const handleSend = async () => {
    if (!input.trim()) return;
    
    // 把用户的消息先显示出来
    const userMessage = { role: 'user', content: input };
    setMessages(prev => [...prev, userMessage]);
    const currentInput = input;
    setInput('');
    setLoading(true);
    
    // AI 的回复用流式方式获取
    const aiMessage = { role: 'assistant', content: '' };
    setMessages(prev => [...prev, aiMessage]);
    
    try {
      // 这里使用 fetch API 处理流式响应
      const response = await fetch(${API_BASE_URL}/chat/completions, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': Bearer ${API_KEY}
        },
        body: JSON.stringify({
          model: 'gpt-4.1',
          messages: [...messages, userMessage],
          stream: true  // 开启流式输出
        })
      });
      
      // 获取流式数据的读取器
      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      
      // 循环读取数据
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        
        // 解码二进制数据
        const chunk = decoder.decode(value);
        // 每行数据以 data: 开头
        const lines = chunk.split('\n');
        
        for (const line of lines) {
          if (line.startsWith('data: ')) {
            const data = line.slice(6);
            if (data === '[DONE]') continue; // 流式结束标记
            
            try {
              const parsed = JSON.parse(data);
              const content = parsed.choices?.[0]?.delta?.content || '';
              if (content) {
                // 逐字追加到 AI 回复中
                setMessages(prev => {
                  const updated = [...prev];
                  updated[updated.length - 1].content += content;
                  return updated;
                });
              }
            } catch (e) {
              // 忽略解析错误
            }
          }
        }
      }
    } catch (error) {
      console.error('请求出错:', error);
      setMessages(prev => {
        const updated = [...prev];
        updated[updated.length - 1].content = '抱歉,发生了错误。';
        return updated;
      });
    } finally {
      setLoading(false);
    }
  };
  
  return (
    <div style={{ maxWidth: '600px', margin: '50px auto', padding: '20px' }}>
      <h2>AI 流式对话演示</h2>
      <div style={{ 
        border: '1px solid #ddd', 
        height: '400px', 
        overflowY: 'auto',
        padding: '15px',
        marginBottom: '15px',
        backgroundColor: '#f9f9f9'
      }}>
        {messages.map((msg, idx) => (
          <div key={idx} style={{ 
            textAlign: msg.role === 'user' ? 'right' : 'left',
            marginBottom: '10px'
          }}>
            <span style={{
              display: 'inline-block',
              padding: '8px 12px',
              borderRadius: '10px',
              backgroundColor: msg.role === 'user' ? '#007bff' : '#e0e0e0',
              color: msg.role === 'user' ? '#fff' : '#333'
            }}>
              {msg.content}
            </span>
          </div>        ))}
      </div>
      <div style={{ display: 'flex', gap: '10px' }}>
        <input
          type="text"
          value={input}
          onChange={e => setInput(e.target.value)}
          onKeyPress={e => e.key === 'Enter' && handleSend()}
          disabled={loading}
          placeholder="输入你的问题..."
          style={{ 
            flex: 1, 
            padding: '10px', 
            fontSize: '16px',
            borderRadius: '5px',
            border: '1px solid #ccc'
          }}
        />
        <button 
          onClick={handleSend}
          disabled={loading}
          style={{
            padding: '10px 20px',
            fontSize: '16px',
            backgroundColor: loading ? '#ccc' : '#007bff',
            color: '#fff',
            border: 'none',
            borderRadius: '5px',
            cursor: loading ? 'not-allowed' : 'pointer'
          }}
        >
          {loading ? '生成中...' : '发送'}
        </button>
      </div>
    </div>
  );
}

export default ChatStream;

把这个组件引入到 App.js 中:

import React from 'react';
import ChatStream from './ChatStream';

function App() {
  return (
    <div className="App">
      <ChatStream />
    </div>
  );
}

export default App;

配置你的 API Key

打开 HolySheep AI 官网 注册账号后,在控制台找到你的 API Key。把它复制到上面的代码中,替换掉 YOUR_HOLYSHEEP_API_KEY 这段文字。

实际项目推荐把 Key 放到环境变量里,创建 .env 文件:

REACT_APP_HOLYSHEEP_API_KEY=你的真实Key

然后在代码里这样读取:

const API_KEY = process.env.REACT_APP_HOLYSHEEP_API_KEY;

别忘了把 .env 加入 .gitignore,防止 Key 泄露!

启动项目看效果

在终端执行:

npm start

浏览器会自动打开 http://localhost:3000,你应该能看到聊天界面了。输入问题,点击发送,AI 的回复会一个字一个字地出现——这就是流式输出的效果。

我第一次看到这个效果时真的激动了一下,比等好几秒再一次性看到答案体验好太多了。

价格与性能参考

HolySheep AI 的定价在国内平台中非常有竞争力,以下是几个常用模型的价格参考(2026 年最新):

汇率按 ¥1=$1 计算,微信和支付宝可以直接充值,对国内开发者非常友好。我用 DeepSeek V3.2 做过一个文本处理小工具,每个月成本才几块钱。

常见报错排查

报错一:401 Unauthorized - API Key 无效

错误信息:

TypeError: Failed to fetch
401 {"error": {"message": "Invalid API key provided", "type": "invalid_request_error"}}

原因:API Key 填错了、复制时多了空格、或者 Key 已经被删除。

解决方案:

// 检查 Key 格式,确保没有多余空格
const API_KEY = 'sk-xxxxxxxxxxxx' // 直接粘贴,不要有空格

// 或者加个日志打印确认
console.log('API Key 前5位:', API_KEY.substring(0, 5));

报错二:403 Forbidden - 余额不足

错误信息:

403 {"error": {"message": "Insufficient credits", "type": "insufficient_quota"}}

原因:账户余额用完了,HolySheep AI 注册赠送的额度也消耗完了。

解决方案:登录控制台充值,充值后立刻就能用。或者检查是否调用了错误的模型,某些模型价格更高。

报错三:CORS 跨域错误

错误信息:

Access to fetch at 'https://api.holysheep.ai/v1/chat/completions' 
from origin 'http://localhost:3000' has been blocked by CORS policy

原因:浏览器出于安全考虑阻止了前端直接请求 API。这个问题在开发环境很常见。

解决方案:在后端添加一个代理接口,让前端请求自己的服务器,由服务器转发到 AI API。创建一个简单的 Express 服务:

// server.js
const express = require('express');
const cors = require('cors');
const axios = require('axios');
const app = express();

app.use(cors());
app.use(express.json());

app.post('/api/chat', async (req, res) => {
  try {
    const response = await axios.post(
      'https://api.holysheep.ai/v1/chat/completions',
      req.body,
      {
        headers: {
          'Content-Type': 'application/json',
          'Authorization': Bearer ${process.env.HOLYSHEEP_API_KEY}
        },
        responseType: 'stream'
      }
    );
    
    // 把流式响应转发给前端
    res.setHeader('Content-Type', 'text/event-stream');
    response.data.pipe(res);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3001, () => console.log('代理服务运行在 3001 端口'));

然后把前端的请求地址改成 http://localhost:3001/api/chat

报错四:流式数据解析错误

错误信息:

JSON.parse error at chunk: data: {invalid json...

原因:某些情况下服务器返回的数据格式不标准。

解决方案:增强解析容错能力:

// 修改解析部分
for (const line of lines) {
  if (line.startsWith('data: ')) {
    const data = line.slice(6).trim();
    if (!data || data === '[DONE]') continue;
    
    try {
      const parsed = JSON.parse(data);
      // ... 处理逻辑
    } catch (e) {
      // 静默跳过无效数据块
      continue;
    }
  }
}

总结与下一步

通过这篇教程,你应该已经掌握了:

这套方案我已经用在自己的几个小项目里了,体验非常流畅。如果你觉得配置麻烦,HolySheep AI 也有现成的 SDK 可以用,文档写得挺清楚的。

进阶玩法包括:给对话加上 Markdown 渲染(支持代码高亮)、添加打字机的光标闪烁效果、接入历史记录功能等。这些我会在后续教程里讲到。

有任何问题欢迎在评论区留言,我会尽量解答。

👉 免费注册 HolySheep AI,获取首月赠额度