真实面经题目 · 原创解析
前端如何用 SSE 实现 AI 流式输出,并做好增量渲染和吐字动画?
这题考前端如何用 SSE 承接 AI 流式输出并实现稳定的吐字动画,回答重点是流解析、增量状态、渲染节流、取消重连和边界处理。
真实面经题目 · 原创解析
这题考前端如何用 SSE 承接 AI 流式输出并实现稳定的吐字动画,回答重点是流解析、增量状态、渲染节流、取消重连和边界处理。
我会先说链路:前端通过 EventSource 或 fetch 读取 text/event-stream,后端按 token、句子或事件块持续推送,前端解析事件后把增量内容追加到消息状态。吐字动画不要简单每来一个 chunk 就 setState 一次,因为网络 chunk 和视觉字符不是同一节奏。更好的做法是把服务端增量放入缓冲队列,用 requestAnimationFrame 或固定节拍从队列消费字符或小片段,既能保持流畅,也能避免 React/Vue 高频渲染。还要处理 Markdown、代码块、光标、自动滚动和用户手动滚动冲突。工程上要支持取消请求、超时、错误重试、断线恢复、重复片段去重、结束标记和用量统计。回答时重点不是某个框架,而是 SSE 流解析、缓冲、渲染节流和用户体验边界。
SSE 适合服务端向浏览器持续推送文本事件。AI 后端可以把模型增量、状态、错误和结束信号包装成事件,前端逐步接收并更新一条正在生成的消息。它比轮询实时,比 WebSocket 简单,但主要是服务端到客户端方向。
模型返回的 chunk 可能忽快忽慢,甚至一个 chunk 包含多个字或半段 Markdown。如果每个 chunk 都立即渲染,页面可能抖动或卡顿。应先把增量写入缓冲区,再按稳定节奏消费,用户看到的是平滑吐字,而不是网络抖动。
前端消息状态通常包含 messageId、content、status、cursor、error、usage 和 abortController。收到 delta 追加内容,收到 replace 或 metadata 更新对应字段,收到 done 把状态改成完成。这样可以避免多条流互相覆盖。
Markdown、代码块、列表和表格在流式过程中可能处于未闭合状态。可以用轻量解析、延迟完整渲染、代码块占位和 requestAnimationFrame 批量更新来降低开销。长回答还要做虚拟化或分段渲染,避免内容越长越卡。
用户应能停止生成、重新生成、复制已生成内容、在错误后重试。自动滚动只在用户位于底部时生效,用户手动上滑阅读时不要强行拉回底部。错误状态要保留已生成内容,并提示可以继续或重试。
SSE 可能遇到代理缓冲、连接断开、重复事件、后端超时和浏览器标签页挂起。前端要用事件 id 或消息版本做幂等,使用 AbortController 取消请求,区分用户取消、网络错误和模型错误,并记录首 token 延迟和完整耗时。
如果主要是服务端向前端推送模型增量,SSE 简单且够用;如果需要强双向实时协作、客户端频繁发送控制消息,WebSocket 更合适。
服务端 chunk 受模型、网络和代理影响,节奏不稳定。直接渲染会忽快忽慢,还会造成高频状态更新,应该用缓冲队列平滑消费。
未闭合代码块、表格和列表会让解析结果反复变化。可以分段解析、对代码块做临时态,或在生成结束后做一次完整重渲染。
前端要 abort 当前请求,更新消息状态为已停止,保留已有内容,关闭光标动画,并允许重试或继续生成。