在 React Umi 项目中实现基于 SSE 的流式聊天功能

在使用 ChatGPT 的过程中,AI 回复常常是逐字"打印"出来的,这种"流式输出"极大提升了交互体验。本篇文章将带你在基于 Umi 的 React 应用中,通过 Server-Sent Events(SSE) 实现这一流式聊天功能,并封装一个可复用的 Hook,简洁优雅。


🧠 什么是 SSE?

SSE(Server-Sent Events)是一种基于 HTTP 协议的单向通信方式,允许服务器持续向客户端推送事件。它适用于实时日志、在线通知、AI 输出等应用场景。

与 WebSocket 相比:

  • 更轻量,基于 HTTP
  • 单向(服务器 ➝ 客户端)
  • 支持断线重连

📦 安装依赖

无特别依赖,只需要一个支持 CORS 的 SSE 后端(如 Node.js + Express + OpenAI API)。


🧩 封装 useSSE Hook

我们封装一个可复用的 useSSE Hook,负责建立连接、接收消息、关闭连接等逻辑:

ini 复制代码
ts
复制编辑
// src/hooks/useSSE.ts
import { useEffect, useRef } from 'react';

interface UseSSEOptions {
  onMessage: (data: string) => void;
  onError?: (error: Event) => void;
  onOpen?: () => void;
}

export function useSSE(url: string, options: UseSSEOptions) {
  const eventSourceRef = useRef<EventSource | null>(null);

  useEffect(() => {
    if (!url) return;

    const eventSource = new EventSource(url);
    eventSourceRef.current = eventSource;

    eventSource.onopen = () => {
      options.onOpen?.();
    };

    eventSource.onmessage = (event) => {
      options.onMessage(event.data);
    };

    eventSource.onerror = (error) => {
      options.onError?.(error);
      eventSource.close();
    };

    return () => {
      eventSource.close();
    };
  }, [url]);
}

💬 实现聊天页面

我们在 Chat.tsx 页面中,调用这个 Hook,构建完整的聊天界面:

typescript 复制代码
tsx
复制编辑
// src/pages/Chat.tsx
import React, { useState } from 'react';
import { useSSE } from '@/hooks/useSSE';

const Chat: React.FC = () => {
  const [messages, setMessages] = useState<string[]>([]);
  const [input, setInput] = useState('');
  const [chatUrl, setChatUrl] = useState<string | null>(null);

  useSSE(chatUrl || '', {
    onMessage: (data) => {
      setMessages((prev) => {
        const newMessages = [...prev];
        if (typeof data === 'string') {
          newMessages[newMessages.length - 1] += data;
        }
        return newMessages;
      });
    },
    onOpen: () => {
      setMessages((prev) => [...prev, '']); // 占位符
    },
    onError: (err) => {
      console.error('SSE error', err);
    },
  });

  const handleSend = () => {
    if (!input.trim()) return;
    setChatUrl(`http://localhost:3000/sse?prompt=${encodeURIComponent(input)}`);
    setInput('');
  };

  return (
    <div style={{ padding: 24 }}>
      <h2>🤖 与 AI 聊天</h2>
      <div
        style={{
          border: '1px solid #ccc',
          height: 300,
          overflowY: 'auto',
          padding: 12,
          marginBottom: 12,
        }}
      >
        {messages.map((msg, idx) => (
          <p key={idx}>{msg}</p>
        ))}
      </div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        style={{ width: '80%' }}
        placeholder="请输入你的问题..."
      />
      <button onClick={handleSend} style={{ marginLeft: 8 }}>
        发送
      </button>
    </div>
  );
};

export default Chat;

✅ 测试效果

确保你有一个兼容 SSE 的后端接口,例如:

vbnet 复制代码
vbnet
复制编辑
GET /sse?prompt=你好
Content-Type: text/event-stream

启动项目后,打开聊天页面,输入问题,即可看到 AI 的回答像打字机一样"逐字"输出。


📌 优化建议

  • 增加 loading/loading dot 动画
  • 使用对话上下文管理完整会话
  • 对 SSE 数据格式进一步封装(如多字段 JSON)

📚 参考资料


如果你觉得本文对你有帮助,不妨点个赞、收藏支持一下 ❤️

如有问题或想法,欢迎评论区交流~

相关推荐
kyriewen12 小时前
我手写了一个 EventEmitter,面试官追问了 6 个问题——第 4 个我没答上来
前端·javascript·面试
IT_陈寒12 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
小林攻城狮13 小时前
使用 Transport 节流解决 Vercel AI SDK 流式渲染卡死问题
前端·react.js
前端缘梦13 小时前
告别 TS 运行时类型漏洞!Zod 完整入门实战教程(前端 / 全栈必备)
前端·react.js·全栈
the_answer13 小时前
Webpack vs Vite 深度对比分析
前端·webpack
转转技术团队13 小时前
验证码识别实战:前端不写页面,改训模型了?
前端
MomentYY13 小时前
Temperature:AI 的“脑洞旋钮”
前端·llm·ai编程
远航_14 小时前
OpenSpec 完整详细介绍
前端·后端
召钱熏14 小时前
状态枚举正确≠渲染正确:一个语音按钮的状态机边界修复实录
android·前端
SkyWalking中文站14 小时前
认识 Horizon UI · 1/17:SkyWalking 新一代可观测性控制台
运维·前端·监控