DirectSurface UIDirectSurface UI
开始使用
文档/业务指南

DevTools 开发工具

import { RenderStackPanel, RenderText, type AppHost } from 'ds-ui'

DevTools 是 DirectSurface UI 的开发工具窗口。它把 Layout Inspector、Runtime Diagnostics 和业务侧自定义诊断页统一放在一个可停靠窗口里,适合排查复杂界面的布局、命中、刷新请求、网络请求和现场问题。

DevTools 由 AppOverlayService 管理。默认内置两个 tab:

Tab key 用途
Layout layout-inspector 查看 render tree、hit path、节点状态、app context 和选中对象。
Runtime runtime 查看 paint/layout request、热点聚合、调用栈和诊断快照。

业务应用可以注册额外 tab,例如 Network、Logs、State、Feature Flags。框架只负责窗口、停靠、tab 容器和生命周期,不关心业务数据来源。

打开方式

应用入口应保留 Application.mount(...).run(...) 返回的 AppHost,再从 host 获取同一应用实例的服务:

function openDevTools(host: AppHost): void {  const uiServices = host.uiServices   uiServices.showDevTools()  uiServices.showDevTools({ activeTab: 'runtime' })  uiServices.toggleDevTools({ activeTab: 'layout-inspector' })  uiServices.activateDevToolsTab('runtime')}

应用也可以把入口挂到菜单、工具栏或快捷键。Layout API 和 DevTools 的 Layout tab 始终绑定到当前 runtime;传入 enableLayoutInspector: true 时,Application 还会注册 Ctrl/Cmd+Shift+I 开发快捷键。该选项默认是 false,业务基座也可以提供自己的菜单入口。

注册自定义 tab

自定义 tab 使用 registerDevToolsTab() 注册,返回值是注销函数:

const unregister = uiServices.registerDevToolsTab({  key: 'network',  label: 'Network',  icon: 'server',  createContent: () => new RequestLogPanel({ store: requestLogStore }),}) // 模块或 shell 销毁时调用unregister()

RequestLogPanelrequestLogStore 是业务自定义示例,不属于 ds-ui。自定义内容只要返回一个公开的 RenderObject 即可。

如果需要一次性替换全部扩展 tab,可以使用 setDevToolsTabs()

uiServices.setDevToolsTabs([  {    key: 'network',    label: 'Network',    icon: 'server',    createContent: () => new RequestLogPanel({ store: requestLogStore }),  },  {    key: 'logs',    label: 'Logs',    icon: 'file-text',    createContent: () => new RenderText('Logs'),  },])

key 必须唯一,且不能使用内置 key:layout-inspectorruntime

内容生命周期

DevTools 会拥有 tab 内容的生命周期:

  • createContent() 在创建 DevTools 窗口时调用。
  • DevTools 已打开时调用 setDevToolsTabs(),会重新创建扩展 tab 内容。
  • 被移除的扩展 tab 内容会被自动 dispose()
  • 关闭 DevTools 时,内置 tab 和所有扩展 tab 内容都会被 dispose()
  • 再次打开 DevTools 会重新调用 createContent()

因此,扩展 tab 不应复用同一个 RenderBox 实例:

// 推荐:每次打开创建新的内容实例createContent: () => new RequestLogPanel({ store }) // 不推荐:多个窗口或多次打开复用同一个实例const panel = new RequestLogPanel({ store })createContent: () => panel

如果 tab 内容订阅了 store、事件总线或定时器,应在自身 dispose() 中释放:

class RequestLogPanel extends RenderStackPanel {  private readonly _unsubscribe: () => void   constructor(options: { store: RequestLogStore }) {    super({ orientation: 'vertical' })    this._unsubscribe = options.store.subscribe(() => this.refresh())  }   refresh(): void {    // 从 store 读取最新诊断数据并刷新子组件。  }   override dispose(): void {    this._unsubscribe()    super.dispose()  }}

和 Dock Window 的关系

DevTools 默认作为开发者停靠窗口打开,不属于当前业务页面内容。它和主窗口并排显示,避免被业务 AppOverlayHost、弹窗或页面生命周期遮挡。

用户可以在 DevTools 标题栏切换停靠方向或浮动显示。停靠后,和主窗口相邻的边缘可以拖动调整尺寸。

兼容旧入口

旧的 Layout Inspector 和 Runtime Diagnostics 入口仍然可用:

uiServices.showLayoutInspectorPanel()uiServices.showRuntimeDiagnosticsPanel()uiServices.toggleRuntimeDiagnosticsPanel()

这些方法现在会打开 DevTools 并切换到对应 tab。为了兼容旧测试和旧代码:

  • showLayoutInspectorPanel().debugState() 返回 Layout Inspector 的 debug state。
  • showRuntimeDiagnosticsPanel().debugState() 返回 Runtime Diagnostics 的 debug state。

新代码建议直接使用 DevTools API:

const window = uiServices.showDevTools({ activeTab: 'runtime' })window.debugDevToolsState()window.debugLayoutInspectorState()window.debugRuntimeDiagnosticsState()

常见接入模式

网络请求

网络请求 tab 通常由应用基座提供:

  1. HTTP client 统一记录请求开始、结束、耗时、状态和错误。
  2. 请求记录写入应用级 request log store。
  3. Network panel 订阅 store 并展示列表和详情。
  4. Shell 通过 registerDevToolsTab() 把 Network panel 挂到 DevTools。

框架不直接采集业务请求,也不要求应用使用某个 HTTP 实现。

日志和状态

Logs、State 等 tab 也建议由应用基座注册,而不是由每个页面单独注册。这样 DevTools 的生命周期稳定,页面切换不会丢失诊断上下文。

页面级诊断可以写入共享 store,再由基座 tab 统一展示。

使用建议

  • DevTools 只服务开发、测试和现场排障,不替代业务审计。
  • 扩展 tab 的 key 使用稳定英文标识,避免和内置 tab 冲突。
  • tab 内容不要再放自己的关闭按钮,关闭和停靠由 DevTools 窗口负责。
  • 诊断数据要脱敏,不应展示 token、患者隐私、完整病历正文或大体积业务对象。
  • 大列表和大对象应分页、虚拟化或摘要展示,避免诊断工具本身造成卡顿。

相关文档