跳到主要内容

一次前端流式渲染体验优化记录

· 阅读需 2 分钟
一介布衣
全栈开发者

这次优化很小,但非常值钱。问题出在一个对话页:模型流式返回 token 时,我们最初每收到一段就直接 setState 一次,结果页面在长回答场景里会明显抖动,尤其是移动端更明显。

最初大家直觉上以为这是“模型流太慢”或者“前端机器太差”,但真正看性能面板后才发现,问题不是响应慢,而是我们把过多的小更新直接推给了 React 渲染。

最早暴露出来的,不是慢,而是“更新太碎”

典型表现是:

  • 长回答时滚动条抖动
  • 输入框偶尔失去流畅感
  • 页面并不是卡死,但用户明显感觉“有点顿”

我后来更确定的一点:流式体验的关键不是频率,而是节奏

这次问题让我再次确认,流式渲染的关键不是“能不能把 token 打到页面上”,而是“多频更新是否被合并成了用户真正需要感知的节奏”。

如果每个 token 都触发一次视图更新,前端其实是在帮后端把噪音原样放大。

我后来做的,不是让后端变快,而是把渲染节奏和数据节奏分开

后来我们做了一个很简单的改动:

  • 后端 token 继续实时收
  • 前端按更粗一点的节奏批量刷新视图

这不是改变内容,而是改变显示节奏。代码上类似这样:

let buffer = ''
let framePending = false

function onChunk(chunk: string) {
buffer += chunk
if (framePending) return
framePending = true

requestAnimationFrame(() => {
setDraft(buffer)
framePending = false
})
}

这次改动之后,我又顺手补了一层很容易被忽略的处理:把“渲染用草稿”与“最终消息状态”分开。前者可以按帧批量更新,后者只在流结束或关键节点时落正式状态。这样既保住了流式反馈,又不会让整个消息列表跟着高频重排。

我真正想保留的结论

一次前端流式渲染优化最重要的教训是:用户需要的是“稳定更新”,不是“每个 token 都肉眼可见”。把渲染节奏从数据节奏里抽出来,通常比继续盯着模型输出速度更有效。