AI 探索技术手记

前端实现 AI 流式打字机效果:从 SSE 到完美交付

Michael Meng· 2026年7月22日· ◷ 3 分钟阅读· ◉ 1 次浏览
前端实现 AI 流式打字机效果:从 SSE 到完美交付

一、什么是 SSE?为什么 AI 流式必用它?

1. 定义

SSE(Server-Sent Events) 是浏览器原生支持的技术,基于 HTTP 长连接,服务端可以单向推送数据到前端。它无需任何第三方库,浏览器自带 EventSource 对象。

2. 为什么 AI 打字机必须用 SSE?

特性 优势
单向推送 AI 只需要服务端往前端吐文字,不需要前端实时回传
原生支持 浏览器自带 EventSource,零依赖
自动重连 断网后自动重连,比 WebSocket 简单太多
轻量 比 WebSocket 更小、更快、更稳定
文本流友好 完美适配 LLM 逐字返回的场景

3. SSE vs WebSocket

特性 SSE WebSocket
通信方向 仅服务端→前端 双向
协议 HTTP ws/wss
重连 自动 手动实现
数据格式 纯文本 文本/二进制
适用场景 AI 流式输出、通知、日志 聊天、游戏、协同

结论:AI 流式打字机 = SSE 最优解。


二、SSE 实现打字机的核心原理

整个流程可以拆分为五步:

  1. 前端创建 EventSource 连接后端 SSE 接口
  2. 后端以 stream: true 持续返回 JSON 片段
  3. 前端逐块接收、增量渲染 → 形成打字机效果
  4. 监听错误 → 自动重连
  5. 接收结束标志 → 关闭连接

核心思想很简单:不是覆盖,是追加。


三、完整可运行代码

前端核心代码(原生 JS)

<!DOCTYPE html>
<html>
<body>
  <div id="content"></div>
  <script>
    const content = document.getElementById('content');
    let source;

    function initSSE() {
      // 关闭旧连接
      if (source) source.close();

      // 创建新连接
      source = new EventSource('/api/ai/stream', {
        withCredentials: true
      });

      // 接收消息 → 打字机效果
      source.onmessage = (e) => {
        try {
          const data = JSON.parse(e.data);
          const text = data.content || '';
          // 核心:增量追加,不是覆盖
          content.innerHTML += text;
        } catch (err) {}
      };

      // 连接状态
      source.onopen = () => console.log('SSE 连接成功');
      source.onclose = () => console.log('SSE 正常关闭');

      // 最重要:错误处理 + 断线重连
      source.onerror = (err) => {
        console.error('SSE 错误', err);
        source.close();
        setTimeout(initSSE, 1000);
      };
    }

    initSSE();
  </script>
</body>
</html>

React Hooks 版本(面试高频)

import { useState, useEffect, useCallback } from 'react';

export function useAIStream(url: string) {
  const [text, setText] = useState('');
  const [isStreaming, setIsStreaming] = useState(false);

  useEffect(() => {
    let source: EventSource;
    let cancelled = false;

    const connect = () => {
      if (source) source.close();

      source = new EventSource(url);
      setIsStreaming(true);

      source.onmessage = (e) => {
        if (cancelled) return;
        try {
          const data = JSON.parse(e.data);
          if (data.done) {
            setIsStreaming(false);
            source.close();
            return;
          }
          // 函数式更新,避免闭包陷阱
          setText(prev => prev + (data.content || ''));
        } catch (err) {}
      };

      source.onerror = () => {
        source.close();
        setIsStreaming(false);
        if (!cancelled) {
          setTimeout(connect, 1000);
        }
      };
    };

    connect();

    return () => {
      cancelled = true;
      source?.close();
    };
  }, [url]);

  return { text, isStreaming };
}

四、逐行精讲(面试能讲出来就是高手)

1. setText(prev => prev + data.content)

使用函数式更新而非 setText(text + data.content),原因:
- text 是闭包中的旧值
- 高频 SSE 消息可能造成状态更新丢失
- 函数式更新确保每次基于最新值追加

2. 断线重连的黄金法则

source.onerror = () => {
  source.close();        // 必须关闭旧连接
  if (!cancelled) {      // 组件未卸载才重连
    setTimeout(connect, 1000);  // 1s 间隔避免雪崩
  }
};

三个关键点:
- 先 close 再重连,否则内存泄漏
- 检查 cancelled,避免组件卸载后重连
- 延迟重连,避免服务端压力雪崩


五、常见问题与解决方案

Q1:消息乱序怎么办?

SSE 基于 HTTP 长连接,TCP 保证顺序,不会乱序。如果你遇到乱序,检查后端是否用了异步非阻塞写。

Q2:大量用户同时连接,服务端撑不住?

  1. 连接池:后端用异步框架(FastAPI/Fiber),单机可承载万级连接
  2. Nginx 代理:配置 proxy_buffering off,确保流式不缓存
  3. 负载均衡:SSE 要求 sticky session,同一客户端始终发到同一后端

Q3:如何处理跨域?

new EventSource(url, { withCredentials: true });

后端设置 Access-Control-Allow-OriginAccess-Control-Allow-Credentials


六、总结

  • SSE 是浏览器原生技术,无需第三方库
  • 核心思想:增量追加,而非整体替换
  • 断线重连是生产环境必备能力
  • React 中务必使用函数式 setState
  • 配合 TransformStream 可处理更复杂的流式场景

掌握了这些,AI 流式打字机效果对你来说就是小菜一碟。

Comments 留言讨论

还没有评论,来抢个沙发,聊聊你的看法~

Michael.Meng

michaelnews@126.com
用 AI 记录,用文字沉淀

© 2026 Michael Meng · 保留所有权利 · Powered by FastAPI + Nuxt