React实战5分钟搞定流式聊天应用含SSE和Fetch API对比当用户发送消息后需要等待3秒才能看到回复的时代已经过去。现代聊天应用的核心竞争力之一就是实现消息的即时推送和流式渲染。作为React开发者我们手中有两种主流武器传统的Fetch API和专为实时场景设计的SSEServer-Sent Events。今天我将带你在React生态中快速实现这两种方案并深入对比它们的适用边界。1. 项目初始化与环境准备1.1 创建React应用基础框架使用Vite快速搭建开发环境能获得更好的热更新体验npm create vitelatest react-stream-chat --template react-ts cd react-stream-chat npm install1.2 安装必要依赖除了基础React环境我们还需要处理流数据的工具库npm install event-source-polyfill axios提示event-source-polyfill用于在旧版浏览器中支持SSE特性而axios将作为Fetch API的对比方案。2. SSE方案实现2.1 建立事件源连接在React组件中初始化SSE连接import { useEffect, useState } from react import { EventSourcePolyfill } from event-source-polyfill const SSEComponent () { const [messages, setMessages] useStatestring[]([]) useEffect(() { const eventSource new EventSourcePolyfill(/api/chat-stream, { headers: { Authorization: Bearer ${localStorage.getItem(token)} } }) eventSource.onmessage (event) { setMessages(prev [...prev, event.data]) } eventSource.onerror () { console.error(SSE连接异常) eventSource.close() } return () eventSource.close() }, []) return ( div classNamemessage-container {messages.map((msg, index) ( div key{index} classNamemessage-bubble{msg}/div ))} /div ) }2.2 SSE方案特点分析特性SSE实现方案连接方式长连接服务端主动推送协议支持仅HTTP兼容HTTP/2断线重连自动实现数据传输效率文本协议无额外封装开销浏览器兼容性需要polyfill支持IE3. Fetch API流式处理方案3.1 实现分块读取逻辑const FetchStreamComponent () { const [content, setContent] useState() const [isLoading, setIsLoading] useState(false) const startStream async () { setIsLoading(true) try { const response await fetch(/api/chat-stream, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ query: 你好 }) }) if (!response.ok) throw new Error(请求失败) const reader response.body?.getReader() const decoder new TextDecoder() while (reader) { const { done, value } await reader.read() if (done) break const chunk decoder.decode(value) setContent(prev prev chunk) } } catch (error) { console.error(流式请求异常:, error) } finally { setIsLoading(false) } } return ( div button onClick{startStream} disabled{isLoading} {isLoading ? 接收中... : 开始对话} /button div classNamestream-output{content}/div /div ) }3.2 Fetch流式方案特点对比特性Fetch流式方案连接方式短连接需要主动请求协议支持完整HTTP协议栈断线重连需手动实现数据传输效率二进制流需要解码处理浏览器兼容性现代浏览器原生支持4. 两种方案的深度对比4.1 技术选型决策矩阵评估维度SSE方案Fetch流式胜出方案实时性要求高✅❌SSE需要双向通信❌❌WebSocket仅服务端推送✅❌SSE需要携带复杂标头❌✅Fetch大数据量传输⚠️✅Fetch4.2 性能优化技巧SSE优化方案使用EventSourcePolyfill的lastEventId参数实现断点续传服务端配置retry字段控制重连间隔客户端实现消息缓存机制Fetch优化方案采用防抖渲染避免频繁更新DOM实现分块缓存合并处理使用AbortController实现请求取消// 防抖渲染示例 let buffer let renderTimer: number const processChunk (chunk: string) { buffer chunk clearTimeout(renderTimer) renderTimer setTimeout(() { setContent(prev prev buffer) buffer }, 100) }5. 生产环境实战建议5.1 错误处理最佳实践两种方案都需要完善的错误处理机制// SSE错误处理增强版 eventSource.onerror (e) { if (e.readyState EventSource.CLOSED) { console.log(连接正常关闭) } else { console.error(连接异常:, e) setTimeout(() { // 指数退避重连 initSSEConnection() }, Math.min(1000 * 2 ** retryCount, 30000)) } } // Fetch错误处理增强版 try { // ...流处理逻辑 } catch (err) { if (err.name AbortError) { console.log(请求被主动取消) } else { // 上报错误日志 sentry.captureException(err) // 显示用户友好提示 setError(连接不稳定请稍后重试) } }5.2 移动端适配要点网络状态感知监听online/offline事件自动恢复连接省电模式优化在页面不可见时暂停高频率更新触摸反馈增强收到新消息时提供触觉反馈// 可见性变化处理 useEffect(() { const handleVisibilityChange () { if (document.visibilityState visible) { initConnection() } else { cleanupConnection() } } document.addEventListener(visibilitychange, handleVisibilityChange) return () { document.removeEventListener(visibilitychange, handleVisibilityChange) } }, [])在最近的一个客服系统项目中我们最终选择了SSE方案。原因很简单当需要持续接收服务端推送的订单状态变更时SSE的自动重连机制和更简洁的客户端代码让我们的错误处理逻辑减少了约40%。特别是在移动网络不稳定的情况下SSE表现出了更好的健壮性。
React实战:5分钟搞定流式聊天应用(含SSE和Fetch API对比)
React实战5分钟搞定流式聊天应用含SSE和Fetch API对比当用户发送消息后需要等待3秒才能看到回复的时代已经过去。现代聊天应用的核心竞争力之一就是实现消息的即时推送和流式渲染。作为React开发者我们手中有两种主流武器传统的Fetch API和专为实时场景设计的SSEServer-Sent Events。今天我将带你在React生态中快速实现这两种方案并深入对比它们的适用边界。1. 项目初始化与环境准备1.1 创建React应用基础框架使用Vite快速搭建开发环境能获得更好的热更新体验npm create vitelatest react-stream-chat --template react-ts cd react-stream-chat npm install1.2 安装必要依赖除了基础React环境我们还需要处理流数据的工具库npm install event-source-polyfill axios提示event-source-polyfill用于在旧版浏览器中支持SSE特性而axios将作为Fetch API的对比方案。2. SSE方案实现2.1 建立事件源连接在React组件中初始化SSE连接import { useEffect, useState } from react import { EventSourcePolyfill } from event-source-polyfill const SSEComponent () { const [messages, setMessages] useStatestring[]([]) useEffect(() { const eventSource new EventSourcePolyfill(/api/chat-stream, { headers: { Authorization: Bearer ${localStorage.getItem(token)} } }) eventSource.onmessage (event) { setMessages(prev [...prev, event.data]) } eventSource.onerror () { console.error(SSE连接异常) eventSource.close() } return () eventSource.close() }, []) return ( div classNamemessage-container {messages.map((msg, index) ( div key{index} classNamemessage-bubble{msg}/div ))} /div ) }2.2 SSE方案特点分析特性SSE实现方案连接方式长连接服务端主动推送协议支持仅HTTP兼容HTTP/2断线重连自动实现数据传输效率文本协议无额外封装开销浏览器兼容性需要polyfill支持IE3. Fetch API流式处理方案3.1 实现分块读取逻辑const FetchStreamComponent () { const [content, setContent] useState() const [isLoading, setIsLoading] useState(false) const startStream async () { setIsLoading(true) try { const response await fetch(/api/chat-stream, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ query: 你好 }) }) if (!response.ok) throw new Error(请求失败) const reader response.body?.getReader() const decoder new TextDecoder() while (reader) { const { done, value } await reader.read() if (done) break const chunk decoder.decode(value) setContent(prev prev chunk) } } catch (error) { console.error(流式请求异常:, error) } finally { setIsLoading(false) } } return ( div button onClick{startStream} disabled{isLoading} {isLoading ? 接收中... : 开始对话} /button div classNamestream-output{content}/div /div ) }3.2 Fetch流式方案特点对比特性Fetch流式方案连接方式短连接需要主动请求协议支持完整HTTP协议栈断线重连需手动实现数据传输效率二进制流需要解码处理浏览器兼容性现代浏览器原生支持4. 两种方案的深度对比4.1 技术选型决策矩阵评估维度SSE方案Fetch流式胜出方案实时性要求高✅❌SSE需要双向通信❌❌WebSocket仅服务端推送✅❌SSE需要携带复杂标头❌✅Fetch大数据量传输⚠️✅Fetch4.2 性能优化技巧SSE优化方案使用EventSourcePolyfill的lastEventId参数实现断点续传服务端配置retry字段控制重连间隔客户端实现消息缓存机制Fetch优化方案采用防抖渲染避免频繁更新DOM实现分块缓存合并处理使用AbortController实现请求取消// 防抖渲染示例 let buffer let renderTimer: number const processChunk (chunk: string) { buffer chunk clearTimeout(renderTimer) renderTimer setTimeout(() { setContent(prev prev buffer) buffer }, 100) }5. 生产环境实战建议5.1 错误处理最佳实践两种方案都需要完善的错误处理机制// SSE错误处理增强版 eventSource.onerror (e) { if (e.readyState EventSource.CLOSED) { console.log(连接正常关闭) } else { console.error(连接异常:, e) setTimeout(() { // 指数退避重连 initSSEConnection() }, Math.min(1000 * 2 ** retryCount, 30000)) } } // Fetch错误处理增强版 try { // ...流处理逻辑 } catch (err) { if (err.name AbortError) { console.log(请求被主动取消) } else { // 上报错误日志 sentry.captureException(err) // 显示用户友好提示 setError(连接不稳定请稍后重试) } }5.2 移动端适配要点网络状态感知监听online/offline事件自动恢复连接省电模式优化在页面不可见时暂停高频率更新触摸反馈增强收到新消息时提供触觉反馈// 可见性变化处理 useEffect(() { const handleVisibilityChange () { if (document.visibilityState visible) { initConnection() } else { cleanupConnection() } } document.addEventListener(visibilitychange, handleVisibilityChange) return () { document.removeEventListener(visibilitychange, handleVisibilityChange) } }, [])在最近的一个客服系统项目中我们最终选择了SSE方案。原因很简单当需要持续接收服务端推送的订单状态变更时SSE的自动重连机制和更简洁的客户端代码让我们的错误处理逻辑减少了约40%。特别是在移动网络不稳定的情况下SSE表现出了更好的健壮性。