From 8a5209c0847395081c31a0a73a51ec7b58abbfee Mon Sep 17 00:00:00 2001 From: tianqijiuyun-latiao <69459608+tianqijiuyun-latiao@users.noreply.github.com> Date: Wed, 15 Jul 2026 15:26:41 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20perf(data-grid):=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=A4=A7=E6=95=B0=E6=8D=AE=E9=87=8F=E6=A8=AA?= =?UTF-8?q?=E5=90=91=E6=BB=9A=E5=8A=A8=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/DataGrid.layout.test.tsx | 15 ++++-- frontend/src/components/DataGrid.tsx | 46 ++++++++++++------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/DataGrid.layout.test.tsx b/frontend/src/components/DataGrid.layout.test.tsx index 42cf8b6b..c01ca77f 100644 --- a/frontend/src/components/DataGrid.layout.test.tsx +++ b/frontend/src/components/DataGrid.layout.test.tsx @@ -2616,18 +2616,23 @@ describe('DataGrid layout', () => { expect(source).toContain('applyVirtualHorizontalOffset(tableContainer, nextLeft, { forceInternalScroll: true });'); expect(source).toContain('}, [horizontalScrollVisible, scheduleVirtualHorizontalAlignment, tableRenderData, tableScrollX, virtualEditingCell]);'); expect(source).toContain('tableInstance.scrollTo({ left: clampedOffset, top: holderEl.scrollTop });'); - expect(source).toContain('deferPostScrollVisualReassert?: boolean'); - expect(source).toContain('if (!options?.deferPostScrollVisualReassert) {'); expect(source).toContain('const requestedExternalScrollLeft = pendingExternalScrollLeftRef.current ?? latestExternalScroll.scrollLeft;'); - expect(source).toContain('deferPostScrollVisualReassert: true'); expect(source).toContain('if (isExternalScrollbarInteractionActive()) {'); - expect(source).toContain('syncVirtualHorizontalVisualOffset(tableContainer, resolvedScrollLeft);'); + expect(source).toContain('const visual = syncVirtualHorizontalVisualOffset(tableContainer, requestedExternalScrollLeft);'); + expect(source).toContain('lastTableScrollLeftRef.current = visual.clampedOffset;'); + expect(source).toContain('applyVirtualHorizontalOffset(tableContainer, resolvedScrollLeft, { forceInternalScroll: true });'); + expect(source).not.toContain('deferPostScrollVisualReassert'); expect(source).not.toContain('const synced = syncVirtualHorizontalVisualOffset(tableContainer, externalScroll.scrollLeft);'); expect(source).toContain('const handleExternalHorizontalScrollPointerDown = useCallback'); expect(source).toContain('const handleExternalHorizontalScrollPointerRelease = useCallback'); expect(source).toContain('const finishExternalScrollbarDrag = useCallback'); expect(source).toContain('const handleExternalHorizontalScrollLostPointerCapture = useCallback'); - expect(source).toContain('externalScrollbarDraggingRef.current\n || Date.now() < externalScrollInteractionUntilRef.current'); + const interactionActiveSource = source.slice( + source.indexOf('const isExternalScrollbarInteractionActive = useCallback'), + source.indexOf('const clearExternalScrollbarInteraction = useCallback'), + ); + expect(interactionActiveSource).toContain('externalScrollbarDraggingRef.current'); + expect(interactionActiveSource).toContain('Date.now() < externalScrollInteractionUntilRef.current'); const finishExternalScrollbarDragSource = source.slice( source.indexOf('const finishExternalScrollbarDrag = useCallback'), source.indexOf('const handleExternalHorizontalScrollPointerRelease = useCallback'), diff --git a/frontend/src/components/DataGrid.tsx b/frontend/src/components/DataGrid.tsx index db5ae077..8eeb5200 100644 --- a/frontend/src/components/DataGrid.tsx +++ b/frontend/src/components/DataGrid.tsx @@ -3563,7 +3563,7 @@ const DataGrid: React.FC = ({ const applyVirtualHorizontalOffset = useCallback(( tableContainer: HTMLElement, nextOffset: number, - options?: { forceInternalScroll?: boolean; deferPostScrollVisualReassert?: boolean }, + options?: { forceInternalScroll?: boolean }, ) => { const synced = syncVirtualHorizontalVisualOffset(tableContainer, nextOffset); if (!synced) { @@ -3578,12 +3578,10 @@ const DataGrid: React.FC = ({ syncVirtualHorizontalVisualOffset(tableContainer, clampedOffset); }; const scheduleVisualReassert = () => { - if (!options?.deferPostScrollVisualReassert) { - requestAnimationFrame(() => { - reassertVisual(); - window.setTimeout(reassertVisual, 0); - }); - } + requestAnimationFrame(() => { + reassertVisual(); + window.setTimeout(reassertVisual, 0); + }); }; const tableInstance = tableRef.current; @@ -3893,9 +3891,16 @@ const DataGrid: React.FC = ({ ? readVirtualHorizontalOffset(tableContainer) : lastTableScrollLeftRef.current; if (enableVirtual && tableContainer instanceof HTMLElement) { - const synced = syncVirtualHorizontalVisualOffset(tableContainer, resolvedScrollLeft); - if (synced) { - resolvedScrollLeft = synced.clampedOffset; + // 拖动/滚轮期间只做 DOM 视觉位移;空闲后才在这里一次性同步 + // rc-virtual-list 内部 offsetLeft,避免大结果集每帧触发行渲染。 + const applied = applyVirtualHorizontalOffset(tableContainer, resolvedScrollLeft, { forceInternalScroll: true }); + if (applied) { + resolvedScrollLeft = readVirtualHorizontalOffset(tableContainer); + } else { + const synced = syncVirtualHorizontalVisualOffset(tableContainer, resolvedScrollLeft); + if (synced) { + resolvedScrollLeft = synced.clampedOffset; + } } } lastTableScrollLeftRef.current = resolvedScrollLeft; @@ -3906,7 +3911,7 @@ const DataGrid: React.FC = ({ lastExternalScrollLeftRef.current = latestExternalScroll.scrollLeft; horizontalSyncSourceRef.current = ''; }); - }, [enableVirtual, isExternalScrollbarInteractionActive, readVirtualHorizontalOffset, syncVirtualHorizontalVisualOffset]); + }, [applyVirtualHorizontalOffset, enableVirtual, isExternalScrollbarInteractionActive, readVirtualHorizontalOffset, syncVirtualHorizontalVisualOffset]); const refreshExternalScrollbarInteraction = useCallback(() => { externalScrollInteractionUntilRef.current = Date.now() + EXTERNAL_HORIZONTAL_SCROLL_IDLE_SETTLE_MS; @@ -3959,12 +3964,19 @@ const DataGrid: React.FC = ({ const requestedExternalScrollLeft = pendingExternalScrollLeftRef.current ?? latestExternalScroll.scrollLeft; pendingExternalScrollLeftRef.current = null; const tableContainer = tableContainerRef.current; - // 外部滑块每帧只更新一次;视觉补偿在本轮拖拽结束后再统一校正。 + // 用户连续拖动/滚动时,只写 marginLeft、header.scrollLeft 和固定列 CSS 变量。 + // 不在每一帧调用 Table.scrollTo,否则 rc-virtual-list 会随数据量放大渲染开销。 if (enableVirtual && tableContainer instanceof HTMLElement) { - const applied = applyVirtualHorizontalOffset(tableContainer, requestedExternalScrollLeft, { - forceInternalScroll: true, - deferPostScrollVisualReassert: true, - }); + if (isExternalScrollbarInteractionActive()) { + const visual = syncVirtualHorizontalVisualOffset(tableContainer, requestedExternalScrollLeft); + if (visual) { + lastTableScrollLeftRef.current = visual.clampedOffset; + scheduleExternalHorizontalScrollSettle(syncSequence); + return; + } + } + + const applied = applyVirtualHorizontalOffset(tableContainer, requestedExternalScrollLeft, { forceInternalScroll: true }); if (applied) { scheduleExternalHorizontalScrollSettle(syncSequence); return; @@ -3997,7 +4009,7 @@ const DataGrid: React.FC = ({ lastTableScrollLeftRef.current = requestedExternalScrollLeft; scheduleExternalHorizontalScrollSettle(syncSequence); }); - }, [applyVirtualHorizontalOffset, enableVirtual, refreshExternalScrollbarInteraction, scheduleExternalHorizontalScrollSettle]); + }, [applyVirtualHorizontalOffset, enableVirtual, isExternalScrollbarInteractionActive, refreshExternalScrollbarInteraction, scheduleExternalHorizontalScrollSettle, syncVirtualHorizontalVisualOffset]); const handleExternalHorizontalScrollPointerDown = useCallback((event: React.PointerEvent) => { clearExternalScrollbarInteraction();