Skip to content

渲染一个页面

运行态渲染的目标是:宿主准备好数据,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