From 8f747f4b35b4c2ea37caffcf7fa2742f0da1731f Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Tue, 4 Aug 2026 15:10:53 +0800 Subject: [PATCH 1/4] fix(vtable): avoid loading lazy data in custom render sizing --- ...ble-custom-render-async-dataSource.test.ts | 83 +++++++++++++++++++ .../scenegraph/layout/compute-col-width.ts | 13 ++- .../scenegraph/layout/compute-row-height.ts | 13 ++- 3 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 packages/vtable/__tests__/options/listTable-custom-render-async-dataSource.test.ts diff --git a/packages/vtable/__tests__/options/listTable-custom-render-async-dataSource.test.ts b/packages/vtable/__tests__/options/listTable-custom-render-async-dataSource.test.ts new file mode 100644 index 0000000000..b95653e6f0 --- /dev/null +++ b/packages/vtable/__tests__/options/listTable-custom-render-async-dataSource.test.ts @@ -0,0 +1,83 @@ +import { ListTable, data } from '../../src'; +import { createDiv, removeDom } from '../dom'; + +(global as any).__VERSION__ = 'none'; + +describe('ListTable customRender with lazy dataSource', () => { + let containerDom: HTMLElement; + + beforeEach(() => { + containerDom = createDiv(); + containerDom.style.position = 'relative'; + containerDom.style.width = '300px'; + containerDom.style.height = '200px'; + }); + + afterEach(() => { + removeDom(containerDom); + }); + + test('does not load all lazy records when customRender reads value during auto size computation', () => { + const loadedIndexes = new Set(); + let computationBodyValueCount = 0; + const recordsLength = 1000; + const lazyDataSource = new data.CachedDataSource({ + get(index: number) { + loadedIndexes.add(index); + return { + icon: `https://example.com/${index}.svg`, + name: `name-${index}` + }; + }, + length: recordsLength + }); + + const table = new ListTable(containerDom, { + dataSource: lazyDataSource, + columns: [ + { + field: 'icon', + title: 'Icon', + width: 'auto' + }, + { + field: 'name', + title: 'Name', + width: 120 + } + ], + heightMode: 'autoHeight', + limitMaxAutoWidth: 600, + customRender(args) { + const { row, value, forComputation } = args; + if (row === 0) { + return null; + } + if (forComputation && value !== undefined) { + computationBodyValueCount++; + } + return { + renderDefault: false, + expectedHeight: 40, + expectedWidth: 120, + elements: [ + { + type: 'image', + src: value, + width: 20, + height: 20, + x: 35, + y: 10 + } + ] + }; + } + }); + + expect(computationBodyValueCount).toBe(0); + expect(loadedIndexes.has(recordsLength - 1)).toBe(false); + expect(loadedIndexes.size).toBeLessThan(recordsLength); + + table.release(); + }); +}); diff --git a/packages/vtable/src/scenegraph/layout/compute-col-width.ts b/packages/vtable/src/scenegraph/layout/compute-col-width.ts index a2f83fbade..b7940eefe0 100644 --- a/packages/vtable/src/scenegraph/layout/compute-col-width.ts +++ b/packages/vtable/src/scenegraph/layout/compute-col-width.ts @@ -436,11 +436,12 @@ function computeCustomRenderWidth(col: number, row: number, table: BaseTableAPI) cellRange = table.getCellRange(col, row); spanCol = cellRange.end.col - cellRange.start.col + 1; } + const skipCellValue = shouldSkipCustomRenderCellValueForComputation(col, row, table); const arg = { col: cellRange?.start.col ?? col, row: cellRange?.start.row ?? row, - dataValue: table.getCellOriginValue(col, row), - value: table.getCellValue(col, row), + dataValue: skipCellValue ? undefined : table.getCellOriginValue(col, row), + value: skipCellValue ? undefined : table.getCellValue(col, row), rect: getCellRect(col, row, table), table, originCol: col, @@ -490,6 +491,14 @@ function computeCustomRenderWidth(col: number, row: number, table: BaseTableAPI) return undefined; } +function shouldSkipCustomRenderCellValueForComputation(col: number, row: number, table: BaseTableAPI) { + return ( + table.isListTable() && + !table.isHeader(col, row) && + !(table.internalProps.dataSource as any)?.dataSourceObj?.records + ); +} + /** * @description: 计算指标相关列宽 * @param {number} col diff --git a/packages/vtable/src/scenegraph/layout/compute-row-height.ts b/packages/vtable/src/scenegraph/layout/compute-row-height.ts index dade83a49b..52fb3d5c33 100644 --- a/packages/vtable/src/scenegraph/layout/compute-row-height.ts +++ b/packages/vtable/src/scenegraph/layout/compute-row-height.ts @@ -640,11 +640,12 @@ function computeCustomRenderHeight(col: number, row: number, table: BaseTableAPI cellRange = table.getCellRange(col, row); spanRow = cellRange.end.row - cellRange.start.row + 1; } + const skipCellValue = shouldSkipCustomRenderCellValueForComputation(col, row, table); const arg = { col: cellRange?.start.col ?? col, row: cellRange?.start.row ?? row, - dataValue: table.getCellOriginValue(col, row), - value: table.getCellValue(col, row), + dataValue: skipCellValue ? undefined : table.getCellOriginValue(col, row), + value: skipCellValue ? undefined : table.getCellValue(col, row), rect: getCellRect(col, row, table), table, originCol: col, @@ -692,6 +693,14 @@ function computeCustomRenderHeight(col: number, row: number, table: BaseTableAPI return undefined; } +function shouldSkipCustomRenderCellValueForComputation(col: number, row: number, table: BaseTableAPI) { + return ( + table.isListTable() && + !table.isHeader(col, row) && + !(table.internalProps.dataSource as any)?.dataSourceObj?.records + ); +} + /** * @description: compute text height * @param {number} col From 74616a3492c41d8d9b6e5152be13f8373d954c2a Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Tue, 4 Aug 2026 15:43:22 +0800 Subject: [PATCH 2/4] docs(vtable): add issue 4964 custom render demo --- .../issue-4964-custom-render-async-value.ts | 120 ++++++++++++++++++ packages/vtable/examples/menu.ts | 4 + 2 files changed, 124 insertions(+) create mode 100644 packages/vtable/examples/debug/issue-4964-custom-render-async-value.ts diff --git a/packages/vtable/examples/debug/issue-4964-custom-render-async-value.ts b/packages/vtable/examples/debug/issue-4964-custom-render-async-value.ts new file mode 100644 index 0000000000..06cc21ee24 --- /dev/null +++ b/packages/vtable/examples/debug/issue-4964-custom-render-async-value.ts @@ -0,0 +1,120 @@ +import * as VTable from '../../src'; + +const CONTAINER_ID = 'vTable'; +const RECORD_COUNT = 5000; + +function createIconDataUrl(index: number) { + const color = index % 2 === 0 ? '#1664ff' : '#00a870'; + const svg = ` + + ${index % 10} + `; + + return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`; +} + +function createRecord(index: number) { + return { + icon: createIconDataUrl(index), + name: `name-${index}`, + desc: `row ${index}` + }; +} + +export function createTable() { + const container = document.getElementById(CONTAINER_ID)!; + container.style.width = '800px'; + container.style.height = '500px'; + + const status = document.createElement('div'); + status.style.cssText = 'height: 40px; line-height: 20px; font-size: 13px; color: #333;'; + container.parentElement?.insertBefore(status, container); + + const loadedIndexes = new Set(); + let computationValueCount = 0; + let customRenderCallCount = 0; + + const updateStatus = () => { + const loadedRows = loadedIndexes.size; + const lastRowLoaded = loadedIndexes.has(RECORD_COUNT - 1); + status.innerHTML = [ + `loaded rows: ${loadedRows}/${RECORD_COUNT}, last row loaded: ${lastRowLoaded}`, + `customRender calls: ${customRenderCallCount}, computation value count: ${computationValueCount}` + ].join('
'); + }; + + const dataSource = new VTable.data.CachedDataSource({ + get(index: number) { + loadedIndexes.add(index); + return createRecord(index); + }, + length: RECORD_COUNT + }); + + const option: VTable.ListTableConstructorOptions = { + container, + dataSource, + columns: [ + { + field: 'icon', + title: 'Icon', + width: 'auto' + }, + { + field: 'name', + title: 'Name', + width: 160 + }, + { + field: 'desc', + title: 'Description', + width: 220 + } + ], + heightMode: 'autoHeight', + limitMaxAutoWidth: 600, + customRender(args) { + const { row, value, forComputation } = args; + customRenderCallCount++; + + if (row === 0) { + return null; + } + + if (forComputation && value !== undefined) { + computationValueCount++; + } + + return { + renderDefault: false, + expectedHeight: 40, + expectedWidth: 120, + elements: [ + { + type: 'image', + src: value, + width: 20, + height: 20, + x: 35, + y: 10 + } + ] + }; + } + }; + + const tableInstance = new VTable.ListTable(option); + (window as any).tableInstance = tableInstance; + (window as any).issue4964Status = { + loadedIndexes, + get loadedRows() { + return loadedIndexes.size; + }, + get computationValueCount() { + return computationValueCount; + } + }; + + updateStatus(); + setTimeout(updateStatus, 0); +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index 6fcdf4ccf7..81435c6704 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -78,6 +78,10 @@ export const menus = [ path: 'debug', name: 'issue-4904-frozen-row-gap' }, + { + path: 'debug', + name: 'issue-4964-custom-render-async-value' + }, { path: 'debug', name: 'issue-4798-sort-icon-visible-time' From 14525ef600c20581e72ee01ebd54f6875b6856dd Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Tue, 4 Aug 2026 16:09:26 +0800 Subject: [PATCH 3/4] docs: add changelog for issue 4964 --- ...stom-render-lazy-data-source_2026-08-04-16-08.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/fix-issue-4964-custom-render-lazy-data-source_2026-08-04-16-08.json diff --git a/common/changes/@visactor/vtable/fix-issue-4964-custom-render-lazy-data-source_2026-08-04-16-08.json b/common/changes/@visactor/vtable/fix-issue-4964-custom-render-lazy-data-source_2026-08-04-16-08.json new file mode 100644 index 0000000000..eb9c8a08bc --- /dev/null +++ b/common/changes/@visactor/vtable/fix-issue-4964-custom-render-lazy-data-source_2026-08-04-16-08.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "fix: avoid loading all lazy dataSource records during customRender auto size computation (GitHub #4964)", + "type": "patch" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} From 3a92c445ecbbe9c64c951e4bc7b549a0ca3db5b1 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 5 Aug 2026 14:33:53 +0800 Subject: [PATCH 4/4] docs(vtable): narrow issue 4964 demo custom render --- .../options/listTable-custom-render-async-dataSource.test.ts | 4 ++-- .../examples/debug/issue-4964-custom-render-async-value.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vtable/__tests__/options/listTable-custom-render-async-dataSource.test.ts b/packages/vtable/__tests__/options/listTable-custom-render-async-dataSource.test.ts index b95653e6f0..3e41f3a705 100644 --- a/packages/vtable/__tests__/options/listTable-custom-render-async-dataSource.test.ts +++ b/packages/vtable/__tests__/options/listTable-custom-render-async-dataSource.test.ts @@ -49,8 +49,8 @@ describe('ListTable customRender with lazy dataSource', () => { heightMode: 'autoHeight', limitMaxAutoWidth: 600, customRender(args) { - const { row, value, forComputation } = args; - if (row === 0) { + const { col, row, value, forComputation } = args; + if (row === 0 || col !== 0) { return null; } if (forComputation && value !== undefined) { diff --git a/packages/vtable/examples/debug/issue-4964-custom-render-async-value.ts b/packages/vtable/examples/debug/issue-4964-custom-render-async-value.ts index 06cc21ee24..7a5a6a92c4 100644 --- a/packages/vtable/examples/debug/issue-4964-custom-render-async-value.ts +++ b/packages/vtable/examples/debug/issue-4964-custom-render-async-value.ts @@ -74,10 +74,10 @@ export function createTable() { heightMode: 'autoHeight', limitMaxAutoWidth: 600, customRender(args) { - const { row, value, forComputation } = args; + const { col, row, value, forComputation } = args; customRenderCallCount++; - if (row === 0) { + if (row === 0 || col !== 0) { return null; }