第 9 章:React-free 对象层与快照
本章解决的问题
前端开发者容易把所有状态塞进 React。DeepSeek Harness 的 Web 客户端把业务逻辑放在 React 外,再通过快照喂给 React。这一章解释这套设计。
交互图解:React-free 前端流式运行时 · 查看图解导读
大白话理解
如果每个 token 都触发一次 setState 并让整个聊天列表重渲染,页面会非常慢。
更好的方式是:
text
业务对象层(无 React)
-> 累积流式状态
-> 生成不可变快照
-> 合批通知
-> React 订阅快照这样业务逻辑不依赖 React,React 只负责投影。
必须掌握的概念
1. React-free Object Layer
对象层负责:
- 连接管理。
- 事件接收。
- 流式累积。
- 工具调用树。
- 会话状态。
它不 import React。
2. Immutable Snapshot
业务对象生成一个不可变快照。相同数据返回相同引用,变化时返回新引用。
3. useSyncExternalStore
React 通过 subscribe 和 getSnapshot 订阅外部 store。
4. Notification Batching
多个快速更新可以合并成一次通知,避免每来一个 token 就通知一次。
DeepSeek Harness 对应实现
DeepSeek Harness 的 Web 客户端有三层:
text
runtime object layer
-> web-react glue
-> pure props components关键类:
SessionManager管理多个会话。Session管理单个会话的事件和快照。ConversationNodeAssembler把事件组装成对话节点。Notifier合并通知。
React 使用 useSyncExternalStore 订阅 Session.getSnapshot()。
进一步阅读:
.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.mdpackages/client/runtime/src/client/sessions/session.ts
代码示例
一个最小 Observable Snapshot
ts
type Listener = () => void
class StreamingSession {
private text = ''
private snapshot = { text: '', done: false }
private listeners = new Set<Listener>()
append(delta: string): void {
this.text += delta
this.snapshot = { text: this.text, done: false }
this.emit()
}
complete(): void {
this.snapshot = { text: this.text, done: true }
this.emit()
}
getSnapshot() {
return this.snapshot
}
subscribe(listener: Listener) {
this.listeners.add(listener)
return () => this.listeners.delete(listener)
}
private emit() {
for (const listener of this.listeners) listener()
}
}React 订阅
tsx
import { useSyncExternalStore } from 'react'
function StreamingView({ session }: { session: StreamingSession }) {
const snapshot = useSyncExternalStore(
callback => session.subscribe(callback),
() => session.getSnapshot(),
)
return (
<div>
<p>{snapshot.text}</p>
<p>{snapshot.done ? 'done' : 'streaming'}</p>
</div>
)
}useSyncExternalStore 会帮助 React 避免在相同快照引用下重复渲染。
面试怎么问
问:为什么要把业务状态放在 React 外?
回答要点:
- 流式输出频率高。
- 业务状态机不应该绑死 React。
- 便于测试和复用。
- 可替换 UI 框架。
问:useSyncExternalStore 解决什么问题?
回答要点:
- 订阅 React 外部的可观察状态。
- 支持并发渲染。
- 快照引用稳定时可避免不必要更新。
问:多个 token 到达时如何合批?
回答要点:
- 对象层累积状态。
- 在 microtask 或 animation frame 中重建快照。
- 一次性通知订阅者。
- 避免每个 token 触发一次 React 更新。
自测题
- React-free object layer 的职责是什么?
getSnapshot和subscribe的约定是什么?- 为什么不可变快照对 React 性能重要?
- 什么时候应把状态放对象层,什么时候应放组件 local state?
本章小结
DeepSeek Harness 的前端架构值得重点学习。它把高频流式业务隔离在 React 外,用不可变快照和 useSyncExternalStore 连接视图。
进一步阅读:
packages/client/web-react/src/scoped-slots.tsx.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md