渲染一个页面
运行态渲染的目标是:宿主准备好数据,SDK 只负责渲染。
数据入口
运行态使用 PageRenderPayload:
ts
interface PageRenderPayload {
page: {
id: string
name: string
targets: Array<'pc' | 'mobile' | 'screen'>
}
version: {
version: string
label: string
publishedAt: string
} | null
document: PageDocument
componentVersions: Record<string, ComponentVersionAsset>
}宿主拿到 payload 后直接交给 Renderer:
vue
<HyperCardPageRenderer
:payload="payload"
canvas-id="card-a"
/>多个业务卡片
业务上常见的是一个页面里循环多个卡片,每个卡片都是一个画布。
vue
<div class="grid grid-cols-2 gap-4">
<section v-for="card in cards" :key="card.id" class="h-[520px]">
<HyperCardPageRenderer
:payload="card.payload"
:canvas-id="`card-${card.id}`"
/>
</section>
</div>要求:
- 每个 renderer 给稳定
canvas-id。 - 外层容器负责宽高。
- 页面文档里
layout.canvas可以使用 fill / percent 语义。 - 跨画布调用必须显式带
canvasId。
跨画布调用
ts
window.__HYPERCARD__?.canvases.callComponent(
'card-a',
'table1',
'refresh',
)老的 __HYPERCARD__.runtime.call(instanceId, method) 仍然能用,但多画布场景不推荐。
页面级实例代码覆盖
如果用户只想改当前页面中某一个实例的代码,不要推成组件库新版本。应该走页面级覆盖:
ts
document.componentOverrides = {
timer_1: {
instanceId: 'timer_1',
baseVersionKey: 'timer@v3',
source: {
html: '<div>...</div>',
js: 'export default { ... }',
css: '.root { ... }',
},
contract,
},
}注意:
- 覆盖只影响这个页面的这个实例。
- 其它页面仍然使用组件库正式版本。
- 如果组件库版本升级导致
baseVersionKey不匹配,该 override 会变成 stale,宿主应提示“重新编辑 / 丢弃定制 / 基于当前版本重新创建”。
常见错误
页面空白
优先查:
- payload 里有没有
document.instances。 componentVersions是否包含实例引用的componentVersionKey。layout.canvas是否被设置成 100% 但父容器没有高度。
组件串台
优先查:
canvas-id是否稳定且唯一。- 是否还在用 legacy
runtime.call(instanceId)。 - 组件实例 id 是否在同一个 canvas 内重复。
隐藏组件再恢复,选值/查询条件丢了
0.6.2 起已默认修复:隐藏(instance.hidden)的实例保活(keepHiddenMounted 默认 true),隐藏 → 恢复 不丢组件内部状态。若你显式传了 :keep-hidden-mounted="false"(省内存),隐藏超过约 1.5s 再恢复会重挂、状态复位 —— 去掉该 prop 即恢复默认保活。
组件滚出视口再滚回,选值/查询条件丢了
这跟"隐藏再恢复"是两个不同的场景:
- 隐藏(
instance.hidden,v-show/display:none):vm 不被拆,keepHiddenMounted(默认开)保住,根本不丢状态。 - 滚出视口:长页面的视口虚拟化会把滚出视口超过约 1.5s(
disposeDelayMs)的实例真 dispose 卸掉 vm,滚回来重建是全新组件、data()重跑,用户之前在组件里改的选值(如维度=年)会复位到默认。这不是隐藏,keepHiddenMounted管不到。
0.6.3 提供 opt-in 的快照持久化来覆盖这个场景:
vue
<HyperCardPageRenderer
:payload="payload"
canvas-id="card-a"
:persist-instance-state="true"
/>开启后,实例被 dispose 前拍一份 $data 的可序列化快照,重建时还原,使滚出 → 滚回保留用户选值 / 查询条件。默认 false,需要才开。HyperCardCanvasDesigner 同名 prop 透传给内嵌 Renderer。
或者根本不让它卸载(0.6.5 / 0.6.6):与其"卸载再拍快照还原",可给 :scheduler-options 传 { disposeOnExit: false }(渐进挂载但不卸载)或 { disabled: true }(立即全挂、关虚拟化)—— 滚出视口不再 dispose,从根上就不会重建丢状态,也不会触发宿主重新取数。代价是已挂实例常驻内存(大画布慎用)。即:persistInstanceState =「卸载 + 拍快照还原」,disposeOnExit:false =「压根不卸载」。各档怎么选见 调度器 · RenderSchedulerOptions。
另外,
0.6.3还无条件修了一个独立问题:渲染器每次重渲染都传"内容相同但引用不同"的 custom 值对象,老逻辑会把它无条件写回文档默认、冲掉用户运行时改的选值。这个修复所有宿主常开、无需开关,与上面的滚出视口快照是两回事。
已知限制
开 persistInstanceState 时注意:
- 只保护同步状态:还原是同步跑在组件
mounted之后。若组件在mounted里异步重置状态(如fetch().then(set)),微任务在还原之后才 resolve,会盖过还原值、选值仍丢。组件应把"要被持久化的字段"当 source-of-truth,别在mounted里盲目异步重置。(被持久化字段通常是查询的输入,不该被查询结果反写。) instanceId必须逻辑唯一、不可删后复用:快照按(canvasId, instanceId)+componentId守卫。若删掉一个实例、之后用相同instanceId且相同componentId新建另一个,会继承已删实例的陈旧状态(快照仅画布 dispose / LRU 满 200 才清)。用唯一 id,别删了又复用。- 类实例字段(
ResizeObserver/Map/Set/Date/ DOM / Vue 组件)不进快照,只持久化可序列化的纯数据;custom 只存各键.value。