在使用 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)
📚 参考资料
如果你觉得本文对你有帮助,不妨点个赞、收藏支持一下 ❤️
如有问题或想法,欢迎评论区交流~