Skip to content

消息渲染

概述

消息渲染是 Claude Code 终端 UI 中最性能敏感的部分。长对话可能包含数百条消息,每条消息包含文本、代码块、工具调用等复杂内容。

Source: src/components/Messages.tsx, src/components/VirtualMessageList.tsx

VirtualMessageList — 虚拟滚动列表

Source: src/components/VirtualMessageList.tsx (1081 行)

JumpHandle 接口

typescript
type JumpHandle = {
  jumpToIndex(i: number)       // 跳转到指定消息
  setSearchQuery(q: string)    // 设置搜索查询
  nextMatch()                  // 下一个匹配
  prevMatch()                  // 上一个匹配
  setAnchor()                  // 捕获滚动位置
  warmSearchIndex(): Promise<number>  // 预热搜索索引
  disarmSearch()               // 清除位置高亮
}

高度缓存

typescript
// 按消息 + 终端宽度缓存高度
// 终端调整大小时失效
heightCache: Map<string, number>

搜索优化

typescript
// WeakMap 自动 GC 被替换的消息
const fallbackLowerCache = new WeakMap<RenderableMessage, string>();

// 每次按键只做 indexOf(零 toLowerCase 分配)
function search(msg: RenderableMessage, query: string): boolean {
  let lower = fallbackLowerCache.get(msg);
  if (!lower) {
    lower = extractSearchText(msg).toLowerCase();
    fallbackLowerCache.set(msg, lower);
  }
  return lower.indexOf(query) !== -1;
}

Messages 组件

Source: src/components/Messages.tsx (400+ 行)

LogoHeader 记忆化

typescript
// React.memo 避免 logo 子树触发级联重渲染
const LogoHeader = React.memo(({ agentDefinitions }) => {
  // 如果 logo 变脏,renderChildren 设置 seenDirtyChild=true
  // 禁用后续兄弟节点的 prevScreen blit
  // 长对话中可能导致 150K+ 写/帧
  return <LogoV2 agentDefinitions={agentDefinitions} />;
});

Brief 模式过滤

typescript
function filterForBriefTool(messages, briefToolNames) {
  // Brief-only 模式:
  // 只保留 Brief tool_use 块 + 结果 + 真实用户输入
  // 过滤所有助手文本
  // 跳过 api_metrics 系统消息
}

消息处理管线

typescript
// 标准化 → 构建索引 → 排序 → 过滤
normalizeMessages(rawMessages)    // 扁平化消息结构
buildMessageLookups(messages)     // 创建工具结果索引
reorderMessagesInUI(messages)     // UI 排序
shouldShowUserMessage(msg)        // 过滤谓词

MessageRow 组件

每条消息由 MessageRow 渲染,根据消息类型选择不同的渲染器:

消息类型渲染内容
用户消息用户输入文本
助手文本Markdown 渲染的回复
工具调用工具名称 + 参数 + 权限状态
工具结果执行输出(代码、文件内容等)
系统消息系统通知
思考块折叠的思考过程

性能策略

1. 虚拟滚动

只渲染视口内可见的消息,不渲染视口外的内容。

2. 高度缓存

预计算每条消息的渲染高度,避免重复布局计算。

3. WeakMap 搜索缓存

使用 WeakMap 缓存搜索文本的小写版本。当消息被压缩替换时,旧缓存自动被 GC。

4. LogoHeader 隔离

将 Logo 组件用 React.memo 隔离,防止它变脏时导致级联重渲染。

5. 粘性滚动

typescript
// 自动锚定到底部
stickyScroll: true
// 新消息到来时自动滚动

下一步