流式更新要合并字段而非替换对象

**流式渲染时 `arr[last] = { role, content }` 是整体替换,会把同一对象上后加的 `thinking` 字段整个丢掉;增量更新必须写成 `{ ...prev, content }`。**

一、Bug 形态

// 新增字段
interface ChatMessage { role; content; thinking?: string }

// 出错的地方:整体赋值,不是合并
updated[updated.length - 1] = { role: "assistant", content: assistantContent };

时序上只坏一瞬间:

t=0s    思考事件涌入(617 个)→ thinking 累积到 1742 字   ✅
t=10s   第一个 text 事件到达  → thinking 被整个清空       ❌ 就这一瞬间
t=10s+  后续 text 事件        → 只更新 content

二、为什么特别隐蔽

  • 时间窗口极窄:只在「思考流结束、正文第一个分片到达」的那一帧触发。
  • 只在有思考的请求里出现:上游触发思考是概率性的(实测 4 次里 3 次有),无思考的请求完全正常,看起来像是"偶发"。
  • 传输层是无辜的:用页面内 fetch 直接抓流验证,后端确实发了 N 个 thinking 事件、前端也确实显示过。问题不在网络、不在上游,而在本地 state 更新方式。

三、通用规则

给已有对象新增可选字段时,任何「整体赋值」的更新点都是定时炸弹。 流式 / 增量更新一律 spread 合并({ ...prev, [field]: value }),或者用不可变更新工具。这类 bug 不会报错,只会静默丢字段。

相关