面向对象:准备在 VTable / VTable-Gantt / VTable-Sheet 等上层包中升级
@visactor/vrender*依赖的维护者。资料边界:当前没有在 VChart 仓库中找到一份完整的“VChart 升级 VRender 全流程”正式文档。本指南依据本地可验证材料整理:VRender2 的 D3 state-engine 接入文档、VChart 升级提交与回归测试、以及 VTable 当前调用面抽查。
/Users/bytedance/Documents/GitHub/VRender2/docs/refactor/state-engine/D3_UPPER_LAYER_ADOPTION_GUIDE.md/Users/bytedance/Documents/GitHub/VRender2/docs/refactor/state-engine/D3_FINAL_SUMMARY.md/Users/bytedance/Documents/GitHub/VRender2/docs/refactor/state-engine/graphic-state-animation-refactor-expectation.md/Users/bytedance/Documents/GitHub/VRender2/docs/refactor/state-engine/D3_VCHART_APP_SCOPED_ALIGNMENT_PLAN.md/Users/bytedance/Documents/GitHub/VRender2/docs/refactor/state-engine/D3_POST_ALPHA_WRAPUP_PLAN.md
这些文档给出的稳定口径是:
- Browser 主路径已经按 alpha ready 处理,Node 不应等强宣称。
- 新接入优先使用 app-scoped 入口:
createBrowserVRenderApp()/createNodeVRenderApp(),再由app.createStage()创建 stage。 createStage()仍是 compat surface,新代码不要继续扩展它。- 状态静态真值按
baseAttributes + resolvedStatePatch -> attribute理解,动画不是新的真值源。 - shared-state 以 Group-first ownership 为主模型,
stateProxy只作为实例级 escape hatch,不是共享状态主模型。 - 当前只承诺顶层
graphic.attribute.xxx = value的兼容边界,不应继续依赖任意深层 nested mutation。 - root app creator 是 browser/node 默认 bootstrap 入口,不是细粒度按需装配入口。
本地 VChart 升级相关提交给出迁移证据:
6f66026cf chore: unblock VChart validation on alpha.2ea7a0007c chore: align VChart with VRender alpha app and state2a1b6ca9a chore: enable VChart validation on VRender alpha.86bc51096f test: cover VChart animation static truth with manual tickerdb138714d chore: validate VRender alpha.9 animation fixeb33d40aa fix: stabilize VChart animations on VRender alpha.1539df8001f fix(vchart): align with VRender state and release contracts6fc9f45b3 fix(vchart): let marker exits finish fade-out
关键落点:
- VChart 最终依赖统一到
@visactor/vrender*1.1.0-alpha.15。 packages/vchart/src/compile/stage-app.ts增加了 app 解析、fallback app 复用和引用计数释放。packages/vchart/src/compile/compiler.ts区分 external stage 与 internal stage,防止释放外部传入 stage。packages/vchart/src/util/graphic-state.ts与packages/vchart/src/mark/base/base-mark.ts对齐 VRender 新状态模型。packages/vchart/__tests__/unit/animation/manual-ticker.test.ts用ManualTicker固定动画中间态、最终态和静态真值。packages/vchart/src/component/marker/base-marker.ts修复 marker exit:离场动画期间保留旧 marker 图元,动画结束后再释放。
当前 VTable 仍有明显的 legacy VRender 接入面:
packages/vtable/package.json依赖@visactor/vrender-core/kits/components/animate~1.0.41。packages/vtable/src/vrender.ts使用preLoadAllModule()、loadBrowserEnv(container)、loadNodeEnv(container)、registerRect()等 legacy 装配。packages/vtable/src/scenegraph/scenegraph.ts使用createStage(...)创建主表 stage。packages/vtable-gantt/src/scenegraph/scenegraph.ts和packages/vtable-gantt/src/zoom-scale/DataZoomIntegration.ts也直接使用createStage(...)。- VTable 存在
stateProxy、addState/removeState、自定义布局动画、scrollbar/tooltip/legend release、chart-in-cell 等较多 VRender 交互面,需要分阶段验证。
VRender 不是单包依赖。VTable 升级时应把下列包统一到同一个目标版本,避免 core / kits / components / animate 混用:
{
"@visactor/vrender": "<target>",
"@visactor/vrender-core": "<target>",
"@visactor/vrender-kits": "<target>",
"@visactor/vrender-components": "<target>",
"@visactor/vrender-animate": "<target>"
}VTable 当前主包没有直接依赖 @visactor/vrender 根包,但 app-scoped 推荐入口来自根包。迁移普通 browser/node stage 创建路径时,建议新增根包依赖,而不是继续只从 vrender-core 拼 legacy 装配。
VChart 这次升级的经验不是“在上层到处包防御逻辑”,而是:
- 按 VRender 新契约调用。
- 在低频边界明确 ownership 和 stage/app 生命周期。
- 用回归测试锁定有效用法。
- 热路径避免大量 per-graphic 防御式兜底。
VTable 是大表格/虚拟滚动/大量单元格场景,新增逻辑尤其要避免落在 cell graphic 创建、滚动更新、hover/state 切换、bounds/render 热路径中。
VRender 文档明确 browser alpha ready,但 node 不是 node-complete alpha。VTable 应先完成 browser 主路径,再为 node export、SSR、miniapp、按需装配等路径单独建验证项。
- 建升级分支,记录基线提交和当前
@visactor/vrender*版本。 - 跑当前 VTable 的核心测试和至少一条 browser demo smoke。
- 记录当前 VTable 的关键路径:
ListTable初始化 / updateOption / releasePivotTable初始化 / updateOption / release- frozen row/col、scroll、resize
- hover / selected / menu / tooltip / scrollbar
- custom layout / JSX custom layout
- VTable-Gantt stage 与 DataZoom 独立 stage
- VTable-Sheet 内嵌表格实例 release
建议先跑:
cd /Users/bytedance/Documents/GitHub/VTable
rg '"@visactor/vrender' packages docs tools -g package.json
rg "createStage\\(|new Stage|loadBrowserEnv|loadNodeEnv|preLoadAllModule|registerRect\\(" packages -g '*.ts' -g '*.tsx'
rg "normalAttrs|stateProxy|addState\\(|removeState\\(|useStates\\(|animate\\(|release\\(" packages -g '*.ts' -g '*.tsx'如果用本地 VRender2 验证,先确保 VTable 的安装结果真的指向本地 VRender2,不要只改 package.json:
cd /Users/bytedance/Documents/GitHub/VTable
realpath packages/vtable/node_modules/@visactor/vrender*期望全部指向同一个本地 VRender 仓库对应包,例如:
/Users/bytedance/Documents/GitHub/VRender2/packages/vrender-core
/Users/bytedance/Documents/GitHub/VRender2/packages/vrender-kits
/Users/bytedance/Documents/GitHub/VRender2/packages/vrender-components
/Users/bytedance/Documents/GitHub/VRender2/packages/vrender-animate
如果使用 Rush/pnpm override,参考 VChart 的 common/config/rush/pnpm-config.json:globalOverrides 可以统一覆盖 @visactor/vrender-core、@visactor/vrender-kits、@visactor/vrender-components、@visactor/vrender-animate。如果迁移到根包 app creator,还要覆盖 @visactor/vrender。
VTable 当前的主要创建链是:
packages/vtable/src/vrender.ts
preLoadAllModule()
loadBrowserEnv(container) / loadNodeEnv(container)
register*()
packages/vtable/src/scenegraph/scenegraph.ts
createStage(...)
推荐目标是类似 VChart 的三种 ownership 模式:
| 场景 | app 提供者 | stage 创建者 | release 责任 |
|---|---|---|---|
普通 new ListTable/PivotTable({ container }) |
VTable 内部 fallback shared app | VTable | table.release 释放自己创建的 stage;最后一个表释放后清理 fallback app |
| 同页多个表格或宿主统一运行时 | 宿主/scene/context 提供 app singleton | VTable | table.release 释放自己的 stage;不释放宿主 app |
| 外部传入 stage(如后续支持) | 外部调用方 | 外部调用方 | VTable 只借用,不释放外部 stage/app |
迁移建议:
- 新增一个低频 helper,例如
src/scenegraph/stage-app.ts或src/vrender-app.ts。 - helper 负责:
resolveVRenderApp(option.app, mode)- fallback app 按 browser/node 分域复用
- 引用计数
createStageFromApp(app, params)
- 主表、Gantt、DataZoom 等创建 stage 的地方逐步接入 helper。
- release 时显式区分:
internal-stage-owned:释放 stage,并释放 fallback app refexternal-app-owned:释放 stage,不释放 appexternal-stage-owned:不释放 stage,不释放 app,只移除 VTable 自己挂载的 root group / 事件 / proxy
不要在每个图元创建或滚动更新路径里判断 app/stage ownership。ownership 应在 table/scenegraph 初始化和 release 这些低频边界解决。
VRender 新状态模型的关键点:
baseAttributes + resolvedStatePatch -> attribute
VTable 迁移时应检查:
- 是否把
normalAttrs当作 snapshot/restore 主路径。 - 是否依赖深层
graphic.attribute.xxx.yyy = ...mutation。 - 是否把
stateProxy当成共享状态定义的主路径。 - hover/selected/disabled 等状态是否应收口到 Group-first shared-state 定义。
- 大量单元格 state 切换是否仍保持 paint-only 快路径,不把状态解释逻辑带回 render/pick/bounds 热路径。
建议先保持 stateProxy 的实例级 escape hatch 能跑通,但不要在新代码里扩展它的 shared-state 职责。VTable custom layout/JSX custom layout 中的 stateProxy 应作为专项测试覆盖,尤其是 text 相关状态。
VChart 这次升级最容易暴露的问题集中在动画中断、updateSpec、exit 生命周期和静态真值污染。VTable 对应场景包括:
- 单元格 appear/update 动画。
- custom layout 图元动画。
- hover/selected 状态动画。
- scrollbar / menu / tooltip / legend 等组件动画。
- Gantt task bar、dependency link、mark line、DataZoom 组件动画。
- table release / updateOption 期间仍在运行的动画。
测试形态建议借鉴 VChart manual-ticker.test.ts:
const ticker = new ManualTicker();
ticker.autoStop = false;
const table = new ListTable({
// ...
renderOption: { ticker }
});
// render 后推进到动画中间时刻
ticker.tickAt(start + duration / 2);
expect(graphic.attribute.opacity).toBeGreaterThan(0);
expect(graphic.attribute.opacity).toBeLessThan(1);
// 动画结束后确认最终布局和静态真值
ticker.tickAt(start + duration + 50);
expect(graphic.attribute.x).toBe(expected.x);
expect(graphic.baseAttributes?.x).toBe(expected.x);
expect(graphic.getFinalAttribute?.().x).toBe(expected.x);如果 VTable 侧还没有把 ticker 暴露到 renderOption 或 stage 创建参数,应优先补测试入口,而不是依赖真实时间等待。
VChart marker exit 的问题给 VTable 一个重要提醒:如果图元需要 exit fade-out,不能在创建 exit animation 后马上 detach/release。
正确判断方式:
- 如果希望有 exit 动画:旧图元必须在动画期间仍 attached。
- 中间 tick 应能看到
opacity/fillOpacity/strokeOpacity等属性处于中间值。 - 动画结束后再释放或 detach。
- 如果图元不需要 exit 动画,才可以同步清理。
排查时必须收集:
- tracked graphics 数量与 attached 状态
- ticker 当前时间、exitStart、duration、中间 tick 时间
- 图元 animate 列表或等价动画状态
opacity/fillOpacity/strokeOpacity/visible等属性快照- 是否提前
removeChild/release - 上层是否真的创建了 exit animation config
若最小复现证明 VRender ticker/animate 没有推进,owner 是 VRender;若 VTable 在动画结束前释放图元,owner 是 VTable。
- 所有
@visactor/vrender*版本一致。 - 如果使用本地 VRender2,
realpath node_modules/@visactor/vrender*指向本地仓库。 - 新代码不再新增
createStage()compat caller。 - 普通 browser stage 改走
createBrowserVRenderApp() + app.createStage()。 - node 路径单独标记 readiness,不与 browser 一起宣称完成。
- legacy
loadBrowserEnv(container)/register*()caller 分类:保留为高级自定义装配,或迁移到 app-scoped 默认入口。
-
ListTable创建、updateOption、resize、release 通过。 -
PivotTable创建、updateOption、resize、release 通过。 - release 后 ticker、stage、event listener、tooltip/menu、scrollbar、legend、layoutMap 全部清理。
- 多个 table 同页创建时互不释放对方 stage。
- 最后一个使用 fallback app 的 table 释放后 app 被清理。
- Gantt 主 stage 迁移或明确保留 compat path。
- Gantt DataZoom 独立 stage 有明确 app/stage ownership。
- Gantt release 不遗漏 DataZoom wrapper/canvas/stage。
- Sheet 内部多个 table instance 切换/释放不误释放共享 app。
- chart-in-cell 的 VChart 依赖版本与 VTable 的 VRender 版本策略不冲突。
- hover / selected / disabled 状态视觉正确。
- 大量单元格 hover/selection 不出现明显性能回退。
-
stateProxy覆盖 text 与非 text 图元。 - 不再依赖
normalAttrs作为主恢复源。 - 不固化深层 nested mutation 作为新契约。
- appear/update 中间态可由 ManualTicker 稳定断言。
- 中断动画后最终布局正确,
baseAttributes与finalAttribute不被旧中间值污染。 - exit 动画期间图元仍 attached。
- release 期间动画被正确停止或收尾,不产生悬挂 ticker。
满足任一条件通常应在 VTable 修:
- VTable 仍新增或依赖 deprecated
createStage()普通路径。 - VTable 在动画结束前 detach/release 自己的图元。
- VTable release 不区分 internal/external stage/app ownership。
- VTable 把
normalAttrs或深层attributemutation 当主契约。 - VTable 缺少对自有 custom layout / Gantt / Sheet 生命周期的测试。
满足任一条件应交给 VRender,而不是在 VTable 侧绕开:
- 使用
createBrowserVRenderApp() + app.createStage()的最小 VRender-only 复现失败。 - ManualTicker 推进时 animate/timeline 不执行。
- 图元 attached 且 animation config 已创建,但属性完全不随 tick 变化。
setAttribute/setAttributes正式写路径无法维持baseAttributes + resolvedStatePatch -> attribute。app.release()/stage.release()违反 VRender 文档声明的 ownership。
VRender handoff 最小材料:
版本矩阵:
- @visactor/vrender*: <version or local commit>
- VTable commit: <sha>
最小复现:
1. createBrowserVRenderApp()
2. app.createStage(...)
3. 创建最少图元/组件
4. 触发 state/animate/release
5. ManualTicker tick 到 t=0 / t=mid / t=end
实际结果:
- attached/released 状态
- animate 列表
- attribute/baseAttributes/finalAttribute/resolvedStatePatch 快照
- ticker 时间点
期望结果:
- 中间态应如何变化
- 结束态应如何提交
- 谁应负责 release
调用链:
- VTable 触发路径
- VRender 相关源码路径
cd /Users/bytedance/Documents/GitHub/VTable
rush update
rush build --to @visactor/vtable
cd packages/vtable
./node_modules/.bin/jest __tests__/listTable.test.ts --runInBand
./node_modules/.bin/jest __tests__/pivotTable.test.ts --runInBand实际项目中应按 touched files 和 VTable CI 配置替换为更准确的命令。若改动源码类型接口,必须跑对应 tsc --noEmit 或 Rush compile。
新增或调整类似 VChart 的 manual ticker 测试,至少覆盖:
- appear:中间态不污染静态真值。
- update:排序/resize/filter 后最终布局正确。
- interrupted update:连续 updateOption 后 retained graphic 仍动画到正确终点。
- state animation:hover/selected 过渡不污染
baseAttributes。 - exit:需要 fade-out 的组件在结束前不被 remove/release。
- 单 table create -> release。
- 同页两个 table,释放第一个不影响第二个。
- updateOption 期间 release。
- Gantt + DataZoom release。
- Sheet 多实例切换与 release。
- 外部 canvas / viewBox / node mode 分支。
- dependency bump only:只统一
@visactor/vrender*版本和 lockfile,确认 install/link 正确。 - app/stage ownership:新增低频 helper,迁移主表 stage 创建与 release,补生命周期测试。
- state alignment:收口
normalAttrs/stateProxy/shared-state相关调用,补交互状态测试。 - animation alignment:补 ManualTicker 回归,逐个修 appear/update/exit。
- Gantt/Sheet follow-up:处理独立 stage 和嵌套实例生命周期。
- browser smoke and perf:跑浏览器 demo、滚动大表、hover/selection 批量切换。
- node/custom assembly:单独验证,不随 browser 主路径一起宣称完成。
每一步都应保持小 diff。若某一步暴露 VRender owner 问题,先产出 VRender 最小复现,不在 VTable 热路径绕开。
可以宣布 VTable browser 主路径完成升级,至少需要满足:
- 依赖和 lockfile 中
@visactor/vrender*版本一致。 - 主表 browser stage 创建走 app-scoped 入口,或 compat path 被明确标记为保留原因。
- table release ownership 有测试覆盖。
- hover/selected/stateProxy 关键交互通过。
- animation manual ticker 覆盖至少 appear/update/state/exit 一组代表场景。
- Gantt/DataZoom/Sheet 的独立 stage 有清晰 owner 判断或明确后续项。
- touched package 的 lint/typecheck/test 通过。
- 若 node/custom assembly 未完成,文档和发布说明明确写为未完成项。