一次前端流式渲染体验优化记录
· 阅读需 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 都肉眼可见”。把渲染节奏从数据节奏里抽出来,通常比继续盯着模型输出速度更有效。
