生命周期与释放
本文完整示例使用以下公共导入:
import { DisposableBag, RenderPage } from 'ds-ui'
DirectSurface UI 中的组件可能持有子组件、事件订阅、定时器、缓存、输入会话、弹窗和业务上下文。生命周期管理不完整会导致内存泄露和异常重绘。
常见生命周期
组件通常经历:
- 创建对象。
- attach 到渲染树。
- layout。
- paint。
- 处理交互。
- detach 或 dispose。
attach / detach 代表组件是否连接到当前渲染管线。dispose 代表组件不再使用,应释放资源。
dispose 职责
组件在 dispose() 中应释放自己持有的资源。页面或组件里如果创建了定时器、订阅、事件监听、动画帧或页面级 controller,推荐统一放进 DisposableBag,再在 dispose() 中释放:
class PatientPage extends RenderPage { private readonly disposables = new DisposableBag() constructor() { super() this.disposables.setInterval(() => { this.refresh() }, 30000) } override dispose(): void { this.disposables.dispose() super.dispose() } private refresh(): void { void this.loadLatestData() } private async loadLatestData(): Promise<void> { // 调用业务服务刷新当前页面数据。 }}
这个模式等价于给页面或组件准备一个轻量资源容器。谁创建定时器或订阅,谁把释放函数放进 DisposableBag;谁拥有页面生命周期,谁在 dispose() 中释放这个 bag。
典型资源:
- 子组件。
DisposableBag。- 事件订阅。
- 定时器。
- 动画控制器。
- Popup。
- 输入会话。
- 图片或文档缓存。
- 页面私有 AppContext。
页面关闭
Tab、窗口或业务页面关闭时,应释放页面级资源。页面私有上下文也应随页面关闭销毁,避免浮动窗口或弹窗继续持有旧业务对象。
如果一个组件接收外部传入的 DOM 或 canvas,销毁时不能擅自移除外部拥有的 DOM,只能清理自己创建和持有的资源。
常见泄露来源
- 全局缓存持有文档对象。
- Popup 关闭后还引用页面组件。
- 定时器未清理。
- 事件订阅未取消。
- 输入 composer 会话未结束。
- 页面关闭后仍在请求重绘。
- 动画 controller 持续通知。
检查方式
可以结合 Runtime Diagnostics、浏览器 heap snapshot 和页面销毁按钮做检查:
- 打开复杂页面。
- 加载大数据或大文档。
- 关闭页面并手动销毁应用实例。
- 生成 heap snapshot。
- 查找仍被持有的页面、文档、canvas、popup 和缓存对象。
原则
- 谁创建,谁释放。
- 谁注册,谁取消。
- 页面私有对象跟随页面生命周期。
- 全局对象只能缓存真正跨页面共享且可控的资源。