在前面的章节中,我们介绍了@Visactor/VTable的基本概念与源码架构,然而,要深入理解表格组件库的工作原理,我们需要深入研究其源码。本文将通过单步调试的方法,逐步介绍@Visactor/VTable的生命周期,深入解析其源码中的表格渲染初始化、更新和销毁流程,帮助读者更好地理解和掌握@Visactor/VTable的生命周期。
如果你使用的是 VSCode ,那么可以参照该配置,在 .vscode 文件中填入下面的配置,便可以实现在 VSCode 中进行调试。

应用程序初始化挂载流程
我们以ListTable为例,在 ListTable constructor 中的第一行打上断点。打开控制台,我们顺利的进入到了ListTable的构造器内部。
- VTable\packages\vtable\src\ListTable.ts

可以看到 ListTable 继承了BaseTable,并且支持三种不同的初始化方式。在对不同入参进行适配后,直接调用super方法,进入到BaseTable的初始化。

在进入到 BaseTable 的 constructor 后,直接调用了super 方法,进入到 EventTarget 的初始化。
- VTable\packages\vtable\src\core\BaseTable.ts


EventTarget 类实现了事件发布订阅的机制,以便于在整个组件内部能够进行事件通知。
- VTable\packages\vtable\src\event\EventTarget.ts

继续进行调试,我们可以看到 BaseTable constructor 大部分代码都是对 options 中的配置进行处理,并将其放到 internalProps 和 BaseTable 实例中。
- VTable\packages\vtable\src\core\BaseTable.ts

在对 options 的数据初始化完成后,创建了 EventHandler 实例,EventHandler 类是一个更通用的事件处理器,可用于管理任何目标上任何类型事件的事件监听,使得监听更加高效。在没有传入指定事件的时候,会指定为 resize 事件并默认使用 VTable 内部修改过的 ResizeObserver 类来处理 resize 事件。

在完成 handler 的初始化后,接下来的核心部分就是 element 的创建与挂载,内部会判断有没有通过options传入element,没有的话则通过 createRootElement 创建 element 并将其 appendChild 到 container 中,完成元素的第一次挂载。

- packages\vtable\src\core\BaseTable.ts

完成element的挂载之后,紧接着就初始化了 scenegraph、stateManager、eventManager,分别是场景树、状态管理和交互事件管理。
- packages\vtable\src\core\BaseTable.ts


继续深入 scenegraph 类的初始化,我们可以看到内部使用了 VRender 的 createStage 方法创建 Stage。在 Stage 初始化的时候,传入了 afterRender 回调函数,这样在 Stage 渲染完成后,就会通过 fireListener 触发after_render 生命周期事件,这里的 after_render 就对应了文档 中的 methods 中的 after_render 回调。
- packages\vtable\src\scenegraph\scenegraph.ts


当Stage初始化完成后,就是场景树、图元的初始化和表格外组件的创建。
- packages\vtable\src\scenegraph\scenegraph.ts

- packages\vtable\src\scenegraph\scenegraph.ts


再回到ListTable中,我们能够看到 dataSource 以及 Title 和 EmptyTip 组件的初始化。
- packages\vtable\src\ListTable.ts

注意看这个 dataSource 的这部分逻辑,针对dataSource,VTable 内部做了三种判断,从上往下进行分析:
- packages\vtable\src\ListTable.ts

- Options 中直接传入 dataSource
直接传入dataSource,一般在异步懒加载数据的时候会用到。Vtable 会直接改变实例上的 dataSource,由于 BaseTable 内部对 datasource 进行了代理,这个操作将会触发 createSceneGraph 和 render 方法;BaseTable 上的 render 方法,本质上就是调用 Stage 上的 render 方法,将 canvas 绘制到指定元素。
- packages\vtable\src\core\BaseTable.ts

- Options 中传入 records 或者 dataSource 和 records 两者都没有
这两种情况都会调用setRecords,在 setRecords 中,我们看到了熟悉的2个方法,createSceneGraph 和 render 方法,在这条逻辑判断中,完成了表格的渲染
- packages\vtable\src\ListTable.ts

在分析完 dataSource 的大致处理流程后,我们回头来看下 createSceneGraph 内部做了什么:
- 可以看到在内部针对透视表做了特殊处理,同时实例化了 SceneProxy ,
proxy.ts文件定义了一个SceneProxy类,该模块负责初始化场景树最大行列数的计算、场景树的渐进加载、首屏分组创建等逻辑。
- packages\vtable\src\scenegraph\scenegraph.ts

- 在 proxy 实例初始化完成之后,调用了
createGroupForFirstScreen方法,createGroupForFirstScreen函数的主要用于初始化 Canvas表格的首屏渲染区域,负责计算首屏需要创建的行列数、构建表头、冻结行列、主体区域等核心部分的绘制单元(Group)的填充、单元格图元的创建与样式调整、动态计算行列尺寸、根据配置处理冻结区域布局、分块渲染策略等,实现高性能的初始渲染。
- packages\vtable\src\scenegraph\scenegraph.ts

- packages\vtable\src\scenegraph\group-creater\progress\create-group-for-first-screen.ts

- packages\vtable\src\scenegraph\group-creater\progress\create-group-for-first-screen.ts


- 完成首屏分组创建之后,调用了 afterScenegraphCreated,对行高列宽进行自动调整,完成 场景树 的调整,用于render方法。
- packages\vtable\src\scenegraph\group-creater\progress\create-group-for-first-screen.ts

在完成表格的渲染和各组件的初始化之后,在 ListTable 的最后一行,通过 fireListeners 触发 INITIALIZED 回调,完成生命周期的最后一步,至此初始化完成。
- packages\vtable\src\ListTable.ts

- 大致流程图

更新流程
VTable 提供了五种方法用于更新配置,本次我们从最常用的 updateOption 方法切入。

通过断点分析,发现一开始直接调用了 ListTable 的父类 BaseTable 的 updateOption 方法。

进入到 BaseTable 中,先是将 options 中 ListTable 和 PivotTable 的表格通用的属性提取出来,然后更新像素比和 padding。紧接着就是更新各种属性,包括 widthMode、heightMode 等。
- packages\vtable\src\core\BaseTable.ts

同时还会清空行高和列宽等配置。

调整主题和Icon配置。

调整背景颜色。

释放旧的 title、legend 、 emptyTip 和 layoutMap 实例,同时调用 sceneGraph.clearCells 清空单元格。
- packages\vtable\src\core\BaseTable.ts

- packages\vtable\src\scenegraph\scenegraph.ts

根据新配置,调用updateComponent,updateComponent 内部会调用 scenegraph.updateComponent, scenegraph 中继续调用 updateStyle,重新更新表格外组件样式,这里的更新包括了滚动条、拖拽参考线和冻结列阴影等。
- packages\vtable\src\core\BaseTable.ts

- packages\vtable\src\scenegraph\scenegraph.ts

- packages/vtable/src/scenegraph/component/table-component.ts

更新完外组件样式之后,调用 updateOptionSetState 重新更新 State 中的配置,包括高亮模式、冻结列状态、hover 与 select 状态。
- packages\vtable\src\core\BaseTable.ts

- packages\vtable\src\state\state.ts

之后通过 this._updateSize 重新计算 canvas 宽高。再调用this.eventManager.updateEventBinder 更新滚动事件与文本吸附的绑定。
- packages\vtable\src\event\event.ts

在 updateEventBinder 内部也会顺带去更新绑定迷你图类型的 hover 事件状态。

- packages\vtable\src\event\sparkline-event.ts

接下来依次更新图例、tooltip、下拉菜单等配置;

清除单元格样式、列宽和行高缓存;

更新自定义单元格合并规则(customMergeCell)

- packages\vtable\src\core\utils\get-custom-merge-cell-func.ts

最后重新调整canvas尺寸,至此 BaseTable 内部的updateOption已完成,重新回到ListTable内部;

首先是更新了分页配置、sortState、dataConfig、showHeader、columns 配置;
- packages\vtable\src\ListTable.ts

然后使用 generateAggregationForColumn 去更新每一列的聚合配置;

处理转置、调用 refreshHeader 更新表头、更新列宽配置;

清空 releaseList;

调整 dataSource、或者是调用 setRecords 、亦或是直接调用 render 方法,前面提到过,这三种方法都能直接触发表格渲染,到这个时候表格的更新已经基本完成了。

在更新完数据源后,重新生成了 title 和 emptyTip 实例,这里需要注意的是,旧的title 和 emptyTip 早在 BaseTable 中的 updateOption 过程就已被释放;

由于内部含有异步的渲染操作,所以在 updateOption 的最后,VTable 通过返回 Promise ,确保了能在 await updateOption 的时候 完成表格组件渲染。至此 VTable 的全量更新已经完成。

- 大致流程图

销毁流程
理解更新机制是维护数据的关键,而完整的生命周期管理同样需要规范的销毁流程。当表格无需使用时,应该及时通过销毁流程主动释放不再需要的对象,避免内存泄漏和资源浪费。
VTable 实例提供了 release 方法用于销毁表格事例。

通过在文件中搜索,可以发现 release 定义在 BaseTable 类中,在开始时会通过 isReleased 字段判断是否正在销毁过程中,避免实例重复卸载。接下来的三行代码分别是销毁了 tooltipHandler 和 menuHandler 实例。
- VTable\packages\vtable\src\core\BaseTable.ts

随后调用了父类的 release 方法,上文提到过,BaseTable 继承了 EventTarget,所以本质上是调用 EventTarget.release 去释 放所有的订阅事件。
- VTable\packages\vtable\src\event\EventTarget.ts

随后依次释放 EventHandler实例、eventManager、focusControl、legends、title、emptyTip、layoutMap、stage、proxy。在完成释放后,调用 parentElement.removeChild 移除挂载的 DOM 元素。

在释放完上述组件后,更新 isReleased 状态,避免重复进行 release 操作;清空场景树,internalProps。至此完成了表格组件的整个销毁过程。

总结
1. 初始化流程
核心流程
-
入口:
ListTable/PivotTable构造函数继承BaseTable,处理配置参数后调用super()进入父类初始化。 -
事件初始化:
BaseTable继承EventTarget实现事件订阅机制,支持resize等事件监听。 -
DOM挂载:创建根元素
div并挂载到容器,作为表格渲染的容器。 -
场景图构建:通过
scenegraph模块创建VRender Stage,管理渲染树和首屏渲染逻辑。 -
首屏渲染:
createGroupForFirstScreen创建表头、冻结行列、主体区域图元,动态计算行列尺寸。 -
代理渲染:
SceneProxy管理渐进式渲染策略,优化性能。 -
数据源处理:根据
dataSource或records触发createSceneGraph和render,完成首次渲染。 -
回调触发:通过
fireListeners触发INITIALIZED事件,标志初始化完成。
关键文件
-
ListTable.ts/PivotTable.ts:入口类,处理表类型差异。 -
BaseTable.ts:核心配置处理、DOM挂载、场景图/状态/事件管理器初始化。 -
scenegraph.ts:场景树管理,与VRender交互。 -
create-group-for-first-screen.ts:首屏渲染逻辑实现。
2. 更新流程
核心流程
-
入口方法:
updateOption触发全量更新。 -
配置更新:
-
更新通用属性(主题、尺寸模式、背景等)。
-
清空缓存(行高列宽、单元格样式)。
-
释放旧组件(标题、图例、布局映射)。
-
场景重建:
-
调用
sceneGraph.clearCells清空单元格。 -
重新创建组件(滚动条、冻结阴影)并更新样式。
-
状态同步:更新高亮、选中、冻结等交互状态。
-
异步渲染:通过
Promise确保渲染完成,支持await同步。
关键文件
-
BaseTable.ts:配置更新主逻辑。 -
state.ts:状态管理模块。 -
sparkline-event.ts:迷你图事件处理。
3. 销毁流程
核心流程
-
入口方法:
release() -
资源释放:
-
移除事件监听:通过
EventTarget.release()解除所有订阅。 -
销毁DOM元素:移除根节点及Canvas。
-
清理组件:释放
Tooltip、Menu等组件实例。 -
内存回收:清空场景树、数据引用,触发GC。
关键设计
-
防御性判断:通过
isReleased标志避免重复销毁。 -
层级释放:从子组件到父类逐级清理,确保无残留引用。
关键文件
-
BaseTable.ts:release()方法实现。 -
EventTarget.ts:事件系统销毁逻辑。
本文档由以下人员提供
taiiiyang(https://github.com/taiiiyang)