在 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)

📚 参考资料


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

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

相关推荐
像风一样自由202038 分钟前
HTML与JavaScript:构建动态交互式Web页面的基石
前端·javascript·html
伍哥的传说1 小时前
React 各颜色转换方法、颜色值换算工具HEX、RGB/RGBA、HSL/HSLA、HSV、CMYK
深度学习·神经网络·react.js
aiprtem1 小时前
基于Flutter的web登录设计
前端·flutter
浪裡遊1 小时前
React Hooks全面解析:从基础到高级的实用指南
开发语言·前端·javascript·react.js·node.js·ecmascript·php
why技术1 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
GISer_Jing2 小时前
0704-0706上海,又聚上了
前端·新浪微博
止观止2 小时前
深入探索 pnpm:高效磁盘利用与灵活的包管理解决方案
前端·pnpm·前端工程化·包管理器
whale fall2 小时前
npm install安装的node_modules是什么
前端·npm·node.js
烛阴2 小时前
简单入门Python装饰器
前端·python
袁煦丞3 小时前
数据库设计神器DrawDB:cpolar内网穿透实验室第595个成功挑战
前端·程序员·远程工作