DirectSurface UIDirectSurface UI
开始使用
文档/API 参考

App Context API 参考

本文示例所需的公共入口如下;各代码块可以按实际用量缩减:

import {  APP_CONTEXT_PROVIDER,  AppContextRegistry,  Application,  CommandManager,  DisposableBag,  RenderAppContextScope,  RenderObject,  RenderStackPanel,  RenderWindow,  createAppContext,  createAppContextKey,  type AppContextProvider,} from 'ds-ui'

App Context API 用于在应用、页面、弹窗、局部组件和命令之间传递范围依赖。它不是全局 store 的替代品,而是一个按 render tree 和生命周期查找对象的机制。

典型用途:

  • 应用级:登录用户、租户、权限服务、字典服务、应用配置。
  • 页面级:当前患者、当前文书、页面参数、页面 controller。
  • 弹窗级:打开参数、临时草稿、弹窗内服务。
  • 组件级:局部编辑器 controller、局部缓存、局部命令依赖。

导出清单

核心导出:

  • AppContextKey
  • AppContextRegistry
  • AppContextLookupKey
  • AppContextInitialValues
  • AppContextSetOptions
  • AppContextProvider
  • APP_CONTEXT_PROVIDER
  • createAppContext
  • createAppContextKey
  • isAppContextProvider
  • RenderAppContextScope
  • RenderAppContextScopeOptions

相关能力:

  • RenderObject.getAppContext(key)
  • RenderObject.requireAppContext(key)
  • PipelineOwner.appContext
  • ApplicationRunOptions.context
  • AppHost.context

AppContextKey

AppContextKey<T> 是类型安全的上下文 key。

const appContextRecordKey = createAppContextKey<{ id: string; title: string }>('record.current')
成员 类型 说明
description string 调试用描述。应稳定、可读、能区分业务域。
id symbol 内部唯一标识。每次创建 key 都是新实例。

同一个上下文值的写入端和读取端必须复用同一个 key 实例。不要在函数内部临时创建 key 再读取,否则读不到之前写入的值。

AppContextLookupKey

AppContextLookupKey<T = unknown> 支持两种形式:

类型 适用场景 类型安全
AppContextKey<T> 框架和业务长期使用的依赖。
string 临时调试、简单 demo、兼容旧代码。

正式业务优先使用 createAppContextKey<T>()。字符串 key 容易命名冲突,也无法在读取时获得类型提示。

创建上下文

const appContext = createAppContext()const childContext = appContext.derive()

createAppContext(initial?)

参数 类型 说明
initial AppContextInitialValues 初始值。传入已有 AppContextRegistry 时直接返回该实例。

AppContextInitialValues 支持:

形式 示例 说明
AppContextRegistry createAppContext(existing) 直接复用已有 registry。
Record<string, unknown> createAppContext({ tenant: 'demo' }) 只能产生字符串 key。
Iterable<[AppContextLookupKey, unknown]> createAppContext([[userKey, user]]) 推荐方式,支持 typed key。

AppContextRegistry

AppContextRegistry 是实际的 key-value 注册表,支持父级链、读取、删除、派生和释放。

构造函数:

const appContextRecordKeyForRegistry = createAppContextKey<{ id: string; title: string }>('record.current')const appContextRoot = new AppContextRegistry(undefined, [['tenant', 'demo']])const appContextPage = new AppContextRegistry(appContextRoot, [  [appContextRecordKeyForRegistry, { id: 'r001', title: '入院记录' }],])
参数 类型 说明
parent AppContextRegistry | undefined 父上下文。get() / require() 找不到 own 值时会向父级查找。
initial Record<string, unknown> | Iterable<[AppContextLookupKey, unknown]> 初始值。

属性:

属性 类型 说明
disposed boolean 是否已释放。
parent AppContextRegistry | undefined 父上下文。
revision number 当前 registry 自身版本。set/delete/clear 会递增。

方法:

方法 返回值 说明
set(key, value, options?) this 写入当前 registry。会先释放同 key 的旧值。
setDisposable(key, value) this 等价于 set(key, value, { dispose: true }),适合有 dispose() 的对象。
get(key) T | undefined 从当前 registry 开始向父级查找。字符串 key 返回 unknown
getOwn(key) T | undefined 只读取当前 registry,不查父级。
require(key) T 必需读取。当前和父级都找不到时抛错。
has(key) boolean 当前或父级存在即返回 true
hasOwn(key) boolean 只判断当前 registry。
keys() IterableIterator<AppContextLookupKey> 当前 registry 自己持有的 key。
delete(key) boolean 删除当前 registry 的 key,并释放该值。
clear() void 删除并释放当前 registry 的全部 own 值。
derive(initial?) AppContextRegistry 创建以当前 registry 为 parent 的子上下文。
dispose() void 标记 disposed,并清空当前 registry。不会释放父级。

dispose() 后继续 set()delete()derive() 会抛错。get()getOwn()has() 仍是普通读取行为,但业务不应在页面关闭后继续使用旧 context。

set 释放规则

AppContextSetOptions

字段 类型 说明
dispose boolean | DisposeFn 释放策略。

释放行为:

  • dispose 不传或为 false:只保存值,不自动释放。
  • dispose: true:如果值有 dispose() 方法,删除、覆盖或 clear 时调用它。
  • dispose: () => void:删除、覆盖或 clear 时调用自定义释放函数。
  • set() 写入同 key 新值前,会先释放旧值。
  • delete() 只删除当前 registry 的 own 值,不影响父级同 key 值。
  • clear() 只释放当前 registry own 值,不释放父级。
const controllerKey = createAppContextKey<{ dispose(): void }>('record.controller')const timerKey = createAppContextKey<number>('page.timer')const contextWithDisposables = createAppContext() contextWithDisposables.setDisposable(controllerKey, {  dispose() {    // close page controller  },}) const timerId = window.setInterval(() => undefined, 1000)contextWithDisposables.set(timerKey, timerId, {  dispose: () => window.clearInterval(timerId),}) contextWithDisposables.dispose()

多个定时器、订阅和事件监听应先集中到 DisposableBag,再放入 context:

const pageBagKey = createAppContextKey<DisposableBag>('page.disposables')const pageContextWithBag = createAppContext()const pageBag = new DisposableBag() pageBag.setInterval(() => {  reload()}, 30000) pageContextWithBag.setDisposable(pageBagKey, pageBag)pageContextWithBag.dispose()

读取规则

直接读 registry:

const appContextUserKey = createAppContextKey<{ id: string; name: string }>('session.user')const appContextRoot = createAppContext([[appContextUserKey, { id: 'u001', name: '张医生' }]])const appContextPage = appContextRoot.derive() const user = appContextPage.require(appContextUserKey)user.name

从 render 对象读:

const appContextRenderUserKey = createAppContextKey<{ id: string; name: string }>('session.user')const appContextConsumerRender = undefined as unknown as RenderObject const renderUser = appContextConsumerRender.requireAppContext(appContextRenderUserKey)renderUser.name

RenderObject.getAppContext(key) 会从当前 render 节点开始向父级查找 AppContextProvider,然后再查 owner.appContext。越靠近当前节点的值优先。

RenderObject.requireAppContext(key) 和 registry 的 require() 类似,但能正确区分“key 存在且值为 undefined”与“key 不存在”。

AppContextProvider

任何对象只要实现 [APP_CONTEXT_PROVIDER](),就可以作为上下文提供者。

const providerContext = createAppContext({ tenant: 'demo' }) const providerExample: AppContextProvider = {  [APP_CONTEXT_PROVIDER](): AppContextRegistry {    return providerContext  },}

通常业务不需要自己实现 provider,优先使用 RenderAppContextScopeRenderWindow.setAppContext()RenderModal.setAppContext() 或 workspace/tab 的 context 配置。

RenderAppContextScope

RenderAppContextScope 把上下文挂到 render tree 的某个范围。

构造参数:

参数 类型 默认值 说明
context AppContextRegistry 新建 registry 外部已有上下文。
values Record<string, unknown> | Iterable<[AppContextLookupKey, unknown]> undefined 追加到当前 scope 的初始值。
child RenderBox undefined 被包裹的子树根。
disposeContextOnDispose boolean 未传 context 时为 true 当前 render 释放时是否释放 appContext

属性和方法:

成员 类型 / 返回值 说明
appContext AppContextRegistry 当前 scope 提供的上下文。
child RenderBox | undefined 子节点。
setChild(child?) void 替换子节点。
dispose() void disposeContextOnDispose 决定是否释放上下文。
const appContextScopeRecordKey = createAppContextKey<{ id: string; title: string }>('record.current')const pageContextScope = new RenderAppContextScope({  values: [    [appContextScopeRecordKey, { id: 'r001', title: '入院记录' }],  ],  child: new RenderStackPanel({ spacing: 8 }),})

如果同时传入 contextvaluesvalues 会写入这个外部 context。此时默认不会在 scope dispose 时释放外部 context,除非显式设置 disposeContextOnDispose: true

应用入口上下文

应用启动可注入根上下文:

const appContextHostUserKey = createAppContextKey<{ id: string; name: string }>('session.user')const host = Application.mount('#app').run(new RenderWindow({ title: 'CIS' }), {  context: [    [appContextHostUserKey, { id: 'u001', name: '张医生' }],  ],}) host.context.require(appContextHostUserKey).id

ApplicationRunOptions.context 会成为 PipelineOwner.appContext。所有 render 节点在本地 provider 链找不到值时,会回退到这个根上下文。

宿主框架销毁应用时应调用 host.dispose()。根上下文跟随 host 释放,适合放应用生命周期对象。

弹窗和窗口上下文

RenderWindowRenderModalRenderPromptModal 都实现了 app context provider,并提供 setAppContext(context, { disposeOnDispose? })

Overlay 服务的弹窗 API 通常支持:

  • source:从触发源收集页面上下文。
  • context:传入外部上下文。
  • contextValues:追加本次弹窗局部值。

推荐规则:

  • 弹窗需要当前页面对象时,传入打开来源 source
  • 弹窗临时参数使用 contextValues
  • 弹窗自己创建且需要随弹窗释放的 context,设置 disposeOnDispose
  • 外部复用的 context 不应由弹窗释放。

命令上下文快照

CommandManager 执行和求值时会根据 source 创建 appContext 快照。快照收集顺序是从 source 向父级,再到应用根;越近的 key 越优先。

const appContextCommandRecordKey = createAppContextKey<{ id: string }>('record.current')const appContextCommandManager = new CommandManager() appContextCommandManager.register({  id: 'record.save',  title: '保存',  canExecute: context => Boolean(context.appContext.get(appContextCommandRecordKey)),  execute: context => {    const record = context.appContext.require(appContextCommandRecordKey)    record.id  },})

命令执行时尽量传入触发源,否则只能解析 root scope 和应用级上下文。

调试与诊断

  • Layout Inspector 会显示选中 render 节点可见的 app context。
  • Runtime Diagnostics 快照会包含根上下文摘要。
  • ObjectInspector 可以展开上下文值。
  • revision 可用于诊断当前 registry 是否发生变更。

上下文调试只应展示对象摘要或脱敏值。不要把敏感业务数据无控制地暴露给生产环境诊断入口。

使用边界

  • App Context 解决依赖可见性和生命周期,不负责业务状态同步。
  • 页面私有对象不要放应用根,避免 tab 串数据和页面关闭后仍被引用。
  • 大文档、大图片、大数组不要无节制放入全局上下文。
  • key 定义在稳定模块,读取端和写入端复用同一个 key 实例。
  • 页面、tab、弹窗、组件创建的 controller 应使用 setDisposable() 或自定义 disposer。
  • get() 可能返回 undefined,必需依赖用 require() 并让错误尽早暴露。

相关文档