一、价值聚焦
渲染性能与动态能力全面升级
-
🚀 性能瓶颈突破
- 用户体验:低配老旧机型使用流畅度提升,无卡顿现象,FPS 接近满帧
- 关键指标:页面浏览体感耗时降低 500ms ,INP 降低 50%
- 内存 占用:canvas 渲染模型替代 DOM 方案,内存使用、回收效率显著优化
-
💡 动态功能 开箱即用
- 交互能力:自动列宽、列拖拽调整、多级表头、右键菜单等表格需求开发效率提升
- 扩展潜力:基于VRender 统一渲染引擎,与 VChart 同源技术栈,天然支持单元格内嵌图表、数据上下钻等 BI 场景
-
🛠️ 架构 解耦
-
技术债务清理:通过适配层剥离旧组件库耦合,代码维护成本降低 , 代码量降低
-
核心逻辑: 基于 VTable API 设计,实现业务逻辑与 UI 渲染的解耦;将事件、主题、数据加载等逻辑拆分为独立模块,分批注入
-
二、背景阐述
重构的必然性
-
关键指标劣化
- 页面呈现体感耗时 5s (标准值需 < 2s),用户等待时间过久,影响用户体验,导致用户流失;
- INP 700ms (web vitals 约定 < 200ms good, >500 ms poor ),交互卡顿导致操作中断率激增,操作运维效率降低
- 卡顿显著: 在 Intel i7 macbookpro 浏览、操作报表,FPS 平均 48 帧,且频繁掉帧
-
技术债 制约
- 组件库版本:当前使用组件库0.x版 Table 不支持动态调整列宽、右键菜单、总计等功能,且0.x版本已不再维护,无法平滑升级 1.x 版本,此外升级后无法保证功能与性能问题得到解决
- 维护成本高:业务逻辑与UI渲染深度耦合,因 Table 组件缺失的能力,定制了多处散乱的 hack 逻辑,导致小需求大改动,开发耗时久,测试难以覆盖全
-
业务需求
- 专项治理:团队对报表模块开展易用性提升的专项梳理,专项包含了近20个需求点,旨在提升用户看数体验和效率,发挥与挖掘报表的价值。
- 长期规划:未来可能会支持更大数量级数据的可视化,支持更丰富的 BI 分析能力
三、调研对比
Canvas vs DOM,VTable的优势
- VTable 简介
VisActor 是字节对外开源的,面向叙事的智能可视化解决方案。由 VChart(可视化组件),VTable(数据组件),VGrammar(语法组件),VMind(图表智能生成组件),VRender(渲染组件) 等多个组件组成。
VTable 是 VisActor 可视化体系中的表格组件库,基于可视化渲染引擎 VRender 进行封装。核心能力如下
性能极致:支持百万级数据快速运算与渲染。
多维分析:多维数据自动分析与呈现
表现力强:提供灵活强大的图形能力,无缝融合VChart
选择VTable的核心理由
- 场景:业务场景适配,能解决核心痛点
- 性能:性能表现优异,内存占用低,滚动和动态更新体验流畅
- 功能:丰富的开箱即用基础功能,透视表及 BI 分析能力为后产品迭代有更好想象空间
- 生态:作为VisActor家族一员,与其他生态内产品深度联动,拓展性更高
四、接入设计
架构设计与平滑迁移
- 适配接入:切割业务与 vtable 的之间的耦合,提升各模块的拓展性与可维护性。
实现一个列构造器,定义通用的列配置项,不同的 Table Column 生成逻辑抽离到 adapter 中,通过 build 方法生成 column 配置项。
// 通用列构造器
export class TableColumn {
private column: TableColumn;
private adapter: ColumnAdapter;
constructor(key: string, options?: { adapter?: ColumnAdapter }) {
this.column = { key };
this.adapter = options?.adapter;
}
setTitle(text: string, tooltip?: string) {
this.column.title = {
text,
tooltip,
};
}
// ...
build(args?: unknow) {
const column = this.adapter(this.column, args);
return column;
}
setAdapter(adapter: TColumnAdapter) {
this.adapter = adapter;
return this;
}
}
// VTable adapter
export function deliveryColumnAdapter(column: TableColumn) {
const columnOfVTable = commonColumnAdapterOfVTable(column);
columnOfVTable.icon = cell => {
// ...
};
columnOfVTable.fieldFormat = record => {
// ...
};
return columnOfVTable;
}
// 生产 VTable 的列配置
const getColumnOfVTable = (column:TableColumn, adapter: ColumnAdapter, args?: unknow) => {
column.setAdapter(adapter);
return column.build(args);
}
2. 主题定制:基于 VTable 主题拓展能力,复刻原组件库 Table UI 设计,使页面风格保持一致。 3. 交互模块化: 右键菜单,表格事件,表格 Tooltip,复选框操作,表单后端排序等功能模块化,在 VTable 实例创建时注册使用。
- Icon 巧用: Icon 在 VTable 中的使用灵活,也支持自定义注册、重置。将报表中使用的 Icon 收敛到一个模块下管理,注册后直接使用。
五、实战演练
- VTable 高性能揭秘 ,渲染百万行数据
VRender 作为 VTable 的渲染引擎,提供的按需重绘及合并异步渲染能能力,支持了VTable的高性能渲染需求。如下图所示,在表格的滚动场景,滚动后的渲染是分为渲染缓存能覆盖的区域和渲染需要更新的区域,二者相比重新渲染所有内容速度提升显著。点击查看关于 VTable 更多原理剖析
- 基于 VTable 加载,更新机制,封装一个 Table 组件
VTable 内部的加载流程中经历了模块初始化、数据处理以及场景渲染。VTable 支持全量、单项以及批量三种更新方式
- 全量更新适用于需要对整个配置进行更改的情况
- 单项更新适用于修改少数几个配置项的场景
- 批量更新适用于批量更新多个配置项的情况,需要调用接口重新布局和渲染
下述代码,包含 VTable 实例初始化,模块注册,更新渲染以及实例卸载,所有逻辑封装成 React 组件,对外暴露部分 VTable Options。采用全量个更新方式,对多个配置项进行更改,避免多次布局和渲染,提高页面性能。
// 注册Icon
registerIcon();
const REPORT_CONTAINER_ID = 'basic_report_of_adspark';
const COMMON_STYLE_OPTION: ListTableConstructorOptions = {
heightMode: 'standard',
defaultRowHeight: DEFAULT_ROW_HEIGHT,
defaultHeaderRowHeight: DEFAULT_HEADER_ROW_HEIGHT,
autoFillWidth: true,
};
export const ReportBaseVTable = ({
option,
defaultSort,
}: {
option: Omit<ListTableConstructorOptions, 'container'>;
defaultSort: SortState;
}) => {
// ...
const tableInstance = useRef<ListTable>(null);
const init = () => {
registerCellTooltip(tableInstance.current);
registerCellClick(tableInstance.current);
registerCheckboxStateChangeEvent(tableInstance.current);
registerCellDropdownMenuClick(tableInstance.current);
registerTableSort(tableInstance.current);
// ...
};
useEffect(() => {
if (!option?.columns?.length) {
return;
}
if (!tableInstance.current) {
tableInstance.current = new ListTable({
container: document.getElementById(REPORT_CONTAINER_ID),
});
init();
}
// 批量更新
tableInstance.current?.updateOption({
widthMode: 'autoWidth',
heightMode: 'autoHeight',
...COMMON_STYLE_OPTION,
...option,
theme: CustomTheme,
});
// 单独更新 sort
tableInstance.current?.updateSortState(defaultSort, false);
}, [defaultSort, option]);
useUnmount(() => {
tableInstance.current?.release();
tableInstance.current = null;
});
return (
<div
style={{
height: (tableInstance.current?.records?.length ?? 0) * DEFAULT_ROW_HEIGHT + DEFAULT_HEADER_ROW_HEIGHT,
width: '100%',
}}
id={REPORT_CONTAINER_ID}
></div>
);
};
3. 定制主题
VTable 支持自定义主题,结合项目 UI 风格,基于默认的主题扩展出了一套定制主题。
/**
* 定义主题
*/
export const CustomTheme: ITableThemeDefine = VTable.themes.DEFAULT.extends({
// 表头样式
headerStyle: {
// ...
hover: {
cellBgColor: '#f0f0f0',
},
},
bodyStyle: { fontSize: 12, color: '#333', borderLineWidth: [1, 0, 1, 0], bgColor: '#fff' },
frameStyle: { borderLineWidth: null, borderColor: '#fff' },
// 选中太样式
selectionStyle: {
// ...
},
// 冻结列样式
frozenColumnLine: {
border: {
// ...
},
shadow: {
},
},
// 滚动条样式
scrollStyle: {
// ...
},
// tooltip 样式
tooltipStyle: {
//...
},
});
// 替换主题
const option = {
// ...
theme: CustomTheme
// ...
}
4. 自定义Tooltip
业务场景中,部分单元格的 hover 需要 tooltip 的交互,实现思路监听单元格 mouseenter 事件,获取当前单元格内容和位置信息,判断是否需要 tooltip, 如果需要则使用 showTooltip API 来实现 tooltip 效果,API 支持 tooltip 内容、样式及动画的多个维度的配置。
/**
* 自定义单元格的Tooltip
*/
export function registerCellTooltip(tableInstance: ListTable) {
tableInstance.on('mouseenter_cell', args => {
const { col, row } = args;
// 获取单元格内容
const { dataValue, field } = tableInstance.getCellInfo(col, row);
// ...
// 获取单元格位置信息
const rect = tableInstance.getVisibleCellRangeRelativeRect({ col, row });
// 展示 tooltip
tableInstance.showTooltip(col, row, {
content: getTooltipContent(dataValue, field),
referencePosition: { rect, placement: VTable.TYPES.Placement.top },
className: 'defineTooltip',
disappearDelay: 60,
style: {
bgColor: 'black',
color: 'white',
arrowMark: true,
},
});
});
}
5. 右键菜单
浏览报表时,右键菜单能够提升用户操作报表的效率。VTable 支持自定义右键菜单,实现分为两步
- 定义 menu,配置菜单项的 key 与 text
- 注册菜单事件 dropdown_menu_click, 结合选择菜单项实现业务功能
export enum EContextMenuKey {
// ...
CopyId = 'copyId',
}
export const ContextToMenuMap = {
// ...
[EContextMenuKey.CopyId]: {
key: EContextMenuKey.CopyId,
text: '复制 ID',
},
};
const option = {
// ...
/**
* 菜单配置
*/
menu: {
contextMenuItems: (field: string, row: number, col: number, table?: BaseTableAPI) => [
{
menuKey: EContextMenuKey.CopyId,
text: ContextToMenuMap[EContextMenuKey.CopyId].text,
}
]
//...
}
/**
* 菜单事件注册
*/
export function registerCellDropdownMenuClick(tableInstance: ListTable) {
tableInstance.on('dropdown_menu_click', (args: DropDownMenuEventArgs) => {
// ...
let copy = tableInstance.getCellRawValue(args.col, args.row);
if (args.menuKey === EContextMenuKey.CopyId) {
copy = copy.split(REPORT_DATA_ID_SEPARATOR)?.[0];
}
navigator?.clipboard?.writeText?.(copy);
// ...
});
}
6. 自定义icon
VTable 中 Icon 应用场景广泛,我们可以通过 icon 和 headerIcon 来配置表头及 body 显示的单元格图标,icon 配置项丰富,涉及 Icon 内容,样式以及 tooltip 等,此外,我们可以自定义 icon 注册到表格中使用, 使用时只需配置 icon name 即可。
export enum TableIcon {
// ...
GoToAnchor = 'goToAnchor',
}
/**
* icon 定义
*/
export const ICON_MAP: Record<TableIcon, ColumnIconOption> = {
[TableIcon.GoToAnchor]: {
type: 'svg',
name: TableIcon.GoToAnchor,
svg:'https://xxxx',
width: ICON_WIDHT,
height: ICON_HEIGHT,
// 所处位置
positionType: VTable.TYPES.IconPosition.left,
// tootip 配置
tooltip: {
style: {
arrowMark: true,
},
title: '',
placement: VTable.TYPES.Placement.right,
},
// hover 样式
cursor: 'pointer',
},
};
/**
* 注册 Icon
*/
export function registerIcon() {
Object.values(ICON_MAP).forEach(icon => {
VTable.register.icon(icon.name, icon);
});
}
// 使用 icon
const tableInstance = new VTable.PivotTable({
columns: [
{
field: '',
// ..
headerIcon: TableIcon.GoToAnchor,
icon: TableIcon.GoToAnchor
}
]
});
/**
* icon 点击事件
*/
export function registerIconClick(tableInstance: ListTable) {
tableInstance.on('click_cell', args => {
// 获取Icon
const { targetIcon, originData } = args;
// 点击 Icon 的动作
if (targetIcon?.name === TableIcon.GoToAnchor) {
// ....
});
}
六、效果验证
从技术指标到业务影响的全景呈现
- 浏览流畅
卡顿问题不复存在,对比效果在低配电脑感知更为明显。下图中新报表每页渲染50条数据,原报表渲染30条数据的渲染帧率对比。
- 交互丰富
- 性能指标量化对比
场景 | 原方案 | VTable方案 | 提升 |
---|---|---|---|
交互浏览 | 平均48帧左右,帧率不稳定 | 平均58帧 | +20%,且稳定性提升 |
INP | 740ms | 334ms | +54.8% |
体感耗时 | 5s | 2.6s | +48%(结合结合其他策略共同优化效果) |
-
业务价值
- 高频需求标准化:右键菜单、单击事件等交互功能配置化、模块化的设计复用度高,需求迭代效率提升
- 技术债务清理:旧组件库强耦合代码移除,彻底解决升级困境
-
经验沉淀与未来规划
-
核心经验:
- 渲染引擎选型需匹配业务场景,结合项目历史迭代与技术栈
- 适配层设计是平滑迁移与技术可控性的关键
-
未来演进:
- 可视化融合:VTable + VChart实现图表内嵌表格
- 智能化探索:基于表格数据的二次分析与AI辅助分析
-
欢迎交流
最后,我们诚挚的欢迎所有对数据可视化感兴趣的朋友参与进来,参与 VisActor 的开源建设:
VChart:VChart 官网、VChart Github(欢迎 Star)
VTable:VTable 官网、VTable Github(欢迎 Star)
VMind:VMind 官网、VMind Github(欢迎 Star)
官方网站:www.visactor.io/ 或 www.viactor.com
Discord:discord.gg/3wPyxVyH6m
飞书群(外网):打开链接扫码
微信公众号:打开链接扫码
github:github.com/VisActor