选择最短的正确生命周期
| 作用域 | 何时结束 | 适合的资源 |
|---|---|---|
editor.scope | 实例销毁 | 实例服务、全局监听 |
editor.documentScope | 替换文档或实例销毁 | 当前文件的保存、上传、查询 |
ComponentView mount.scope | 视图卸载或文档替换 | 图表、预览、尺寸观察 |
scope.fork() | 显式取消或父作用域结束 | 一次输入对应的请求 |
documentId 相同不等于同一次文档生命周期。replaceDocument() 会结束旧文档作用域,业务请求应该跟随这个信号,而不是只比较文件名。
资源租约
const request = editor.documentScope.acquire(
async signal => {
const response = await fetch('/api/preview', { signal })
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const url = URL.createObjectURL(await response.blob())
return { value: url, release: () => URL.revokeObjectURL(url) }
},
url => { console.log('Ready preview URL', url) },
)
// The host may cancel this request before replacing the document.
request.dispose()
这是宿主自行实现 /api/preview 的接入形状,不是 MSFE 提供的服务。acquire 只向仍有效的请求提交结果;取消后才到达的租约也会释放。每次获取都要返回自己拥有的 lease,不能把一个共享对象的 dispose 随意交给多个视图。
scope.own(cleanup) 注册幂等清理并返回提前释放函数;dispose() 先 abort,再逆序释放。第三方库若不能取消计算,仍要在使用结果前检查信号,并释放迟到资源。
SourceDefinition.load
字段绑定资源可通过 defineSource<Attrs, Resource>({ load(attrs, signal) }) 返回 { value, release? }。NodeView 从 status 和 resource 读取加载状态,不要自己再发一次相同请求。
attrs 应保存可持久的业务标识,资源值可以是解析后的 URL、只读展示数据或布局结果。密钥、登录令牌和临时 object URL 不写入 Markdown。图片完整接入见 图片与外部资源。
错误与重试是 UI 协议
作用域报告错误并不等于自动显示错误界面。组件负责 loading / ready / error 状态、可读源码回退和重试动作。每次重试先结束旧子作用域;不能同时保留多张图表或多个网络响应写入口。
分配资源后、返回租约之前抛错也必须清理。可以在创建实例后立即注册清理,或用 try/finally 管理部分初始化。图表例子的 完整 renderer 包含观察器与实例释放。
资源完成后写回源码
“请求仍有效”与“原来的源码位置仍有效”是两回事。上传完成后不要用缓存的数字偏移直接插入。创建文档子作用域,并用 captureAnchor() 保留原意图;结果到达后重新取得 target,再通过命令写回。详见 完整异步目标函数。
保存不需要用服务端响应替换全文。顺序写入、失败重试和离开前 flush 的完整接入见 读取与保存。
清理测试
为加载器提供可手动完成的 Promise:先发 A 再发 B,让 A 最后到达;销毁编辑器后完成请求;使用同一 documentId 换文档;让渲染在初始化中途抛错。验证没有旧 UI、旧源码修改、重复 finalizer 或遗留观察器。