乐于分享
好东西不私藏

#每天一道AI面试题 | 字节前端面试真题《AI 对话这种高频流式返回场景,前端会遇到哪些性能问题?》

#每天一道AI面试题 | 字节前端面试真题《AI 对话这种高频流式返回场景,前端会遇到哪些性能问题?》

  这题表面问性能,实际上在问你有没有真正做过流式对话产品

开头导语

表面上,它像是在问:“AI 对话高频流式返回,会有什么性能问题?”

但如果你一上来只说:

  • • 渲染次数太多
  • • 页面会卡
  • • 要做优化

这其实不太够。

因为 AI 对话场景里的性能问题,不只是一个“页面卡不卡”的前端八股题。它背后连着的是:

  • • 流式内容怎么更新
  • • 长消息怎么渲染
  • • 多条消息怎么管理
  • • markdown 和代码块怎么处理
  • • 用户一边看一边滚动时会不会抖
  • • 页面为什么没崩,但体验已经崩了

所以这题真正想听的,通常不是“你知道性能优化这四个字”。而是:你到底有没有做过那种内容一边返回、页面一边长、状态一边变、用户还一边操作的 AI 对话界面。


正文

一、先说结论:AI 对话的性能问题,很多不是“单次渲染太重”,而是“持续更新太频繁”

做普通前端的时候,很多性能问题都比较像“某一下很重”。

比如:

  • • 打开一个复杂页面
  • • 渲染一个很大的表格
  • • 首屏组件太多
  • • 一次性计算量过大

但 AI 对话场景不太一样。它更常见的问题是:每次更新都不一定重,但更新发生得太频繁。

你想一下流式返回的过程:

  • • 服务端不断吐新内容
  • • 前端不断接收片段
  • • 当前消息不断 append
  • • 页面不断重新渲染
  • • 用户还可能正在滚动、选择文本、切换会话、点停止生成

这时候前端面对的就不是一个静态页面。而是一个持续变化中的界面。

所以 AI 对话产品的性能压力,很多时候不在“瞬时爆发”。而在“持续抖动”。


二、最常见的第一个问题:流式内容更新太频繁,导致高频重渲染

这是最典型的。

如果后端每吐一点内容,前端就立刻 setState 一次。那页面就会进入一种很忙的状态:

  • • 文本不断变长
  • • React/Vue 不断触发更新
  • • 相关子组件跟着反复 render
  • • 滚动区域不断重新计算

如果你的消息列表结构再复杂一点,问题会更明显。

比如一条消息里还包含:

  • • markdown 解析
  • • 代码高亮
  • • 数学公式
  • • 表格
  • • 引用来源
  • • 工具执行状态

那每次流片段进来,代价就不只是“多几个字”。而是整条消息相关的渲染链路可能都要重新走一遍。

所以第一类性能问题,往往不是“内容大”。而是:内容在持续增长,而你每增长一点就让整棵树跟着忙一次。


三、第二个问题:长消息越来越长,渲染成本会越来越高

AI 对话和普通聊天的一个很大区别是:一条消息可能特别长。

普通 IM 里,一条消息大多就一两句话。AI 对话里,一条回答可能是:

  • • 几百字
  • • 几千字
  • • 带代码
  • • 带表格
  • • 带多级标题
  • • 甚至像一篇小文档

这意味着什么?

意味着随着流式返回继续,当前这条消息的渲染成本会越来越高。不是固定成本。而是增长成本。

你一开始 append 10 个字,也许没感觉。但等消息长到几千字以后,你再每次追加一点点,都可能触发更重的重排、重绘和组件更新。

所以 AI 对话很容易出现一种现象:开头挺顺,越往后越卡。

这不是错觉。很多时候真的是因为那条消息已经被喂得太大了。


四、第三个问题:markdown、代码高亮、富文本解析会把压力继续放大

很多 AI 对话产品为了更好看,都会把回答按富文本来展示。

比如支持:

  • • markdown
  • • code block
  • • syntax highlight
  • • 表格
  • • 引用块
  • • 列表
  • • LaTeX

这当然能提高可读性。但也会把性能问题放大。

因为这时前端不只是“显示文本”。而是在做:

  • • 文本切分
  • • markdown 解析
  • • AST 构建
  • • 代码高亮
  • • DOM 结构重建

如果你每收到一小段流式内容,就把整段全文重新 parse 一遍,性能很容易出问题。

尤其是代码块场景。因为高亮往往并不便宜。

所以很多时候真正卡的,不是“流式”本身。而是:流式内容驱动了高成本富文本链路的高频重复执行。


五、第四个问题:消息列表变长后,整个对话区域也会越来越重

很多人只盯着“当前正在生成的那条消息”。但其实整个聊天列表也会变成压力源。

因为 AI 对话不是只看最后一条。用户会往上翻。会切回历史。会复制代码。会对比前后回答。

如果页面里已经堆了很多轮对话:

  • • 每轮都很长
  • • 每轮都有 markdown
  • • 每轮都有代码块
  • • 每轮可能还有引用和工具状态

那整个消息列表本身就会变得很重。

这时候问题可能表现成:

  • • 滚动掉帧
  • • 滚动时白屏一下
  • • 定位到底部不稳定
  • • 自动滚动和手动滚动打架
  • • 切会话变慢

所以第四类性能问题,不是单条消息,而是:整个聊天容器随着历史变长,逐渐失去轻盈感。


六、第五个问题:滚动联动很容易出体验型性能问题

AI 对话页面里,滚动几乎永远不是一个简单动作。

因为它经常和这些东西绑在一起:

  • • 新内容不断追加
  • • 自动滚到底部
  • • 用户正在手动往上翻
  • • 用户选中文本
  • • 工具结果区域展开/折叠

这时候最容易出一种很烦的 bug:性能不一定真的炸了,但体验已经抖起来了。

比如:

  • • 你刚往上翻,页面又把你拽回底部
  • • 你正看上一段,新增内容导致布局跳动
  • • 你在复制代码,结果流式更新把位置顶掉
  • • 页面一直在细小抖动,用户说不上哪不对,但就是难受

所以 AI 对话的性能,很多时候不是 CPU 跑满那种“硬卡”。而是交互层面的“软卡”。

页面还活着。但人已经烦了。


七、第六个问题:状态拆得不好,会把性能问题和状态问题搅成一锅

这点特别常见。

如果你的消息状态、流式状态、会话状态、加载状态、错误状态,全都混在一个很大的对象里。那流式内容每更新一次,可能影响的不只是当前那条消息。

它还可能导致:

  • • 整个会话区域一起重渲染
  • • 顶部 header 跟着刷
  • • 输入框状态也被带着变
  • • 不该更新的组件也一起跑了

这时候你会发现:前端看起来是在处理“流式返回性能问题”。其实根子上是:状态边界没切清楚。

所以这题再往深一点,其实还在问:你有没有把 AI 对话页面当成一个“持续变化的状态系统”来设计。


八、面试里如果被问到“怎么优化”,别只说 memo

很多人一到这里就开始背:

  • • memo
  • • useMemo
  • • useCallback
  • • 防抖节流

这些不是没用。但如果只停在这里,味道还是浅。

更像做过项目的答法,通常会从几个层次讲:

1)降低流式更新频率

不是每来一个 token 就立刻刷一次页面。可以做合并、缓冲、按时间片更新。

核心思想是:不要让 UI 更新频率完全等于服务端吐流频率。

2)把“正在生成的消息”和“历史消息”隔离

历史消息尽量稳定,不要跟着当前流一起反复更新。否则整个列表会被拖着跑。

3)富文本链路做分阶段处理

比如:

  • • 先渲染纯文本
  • • markdown 延后解析
  • • 代码高亮按块处理
  • • 重内容区域懒执行

不要在最频繁更新的时候,把最贵的解析链路全打开。

4)长列表要考虑虚拟化或折叠策略

尤其是历史很长、内容很多的时候。否则聊天窗口会越来越重。

5)滚动策略要明确

什么时候自动滚到底部,什么时候尊重用户手动浏览,什么时候暂停联动。这不只是交互设计,也直接影响性能体验。

6)状态边界拆清楚

把当前消息流、消息列表、会话级状态、输入区状态拆开。别让一次 append 搅动整页。

这时候你的回答,就不是“知道几个优化词”。而像“真的踩过坑”。


九、面试里可以怎么组织成一段顺口回答?

你可以这么答:

AI 对话这种高频流式返回场景,前端最典型的性能问题不是单次渲染重,而是持续更新太频繁。每次流式内容进来都会触发当前消息增长、组件重渲染、滚动区域更新,如果再叠加 markdown 解析、代码高亮、长消息渲染和长列表滚动,性能问题会被进一步放大。

然后接着补:

所以优化思路通常不是单点 memo,而是从更新频率控制、当前消息和历史消息隔离、富文本分阶段处理、长列表虚拟化、滚动策略和状态拆分这几个层面一起做。

最后再收一句:

这类场景最怕的不是页面瞬间卡死,而是页面一直在小幅抖动、频繁重算,最后把体验慢慢磨坏。