From 76e74a02fd9e58f21e8af8fa9aed459ce0d494e0 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Wed, 22 Jul 2026 00:20:17 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20perf(data-grid):=20?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=BD=93=E5=89=8D=E9=A1=B5=E6=9F=A5=E6=89=BE?= =?UTF-8?q?=E6=89=AB=E6=8F=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将匹配坐标与命中统计合并为单次单元格遍历,避免重复格式化整页数据。 使用延迟值调度大结果集查找,同时保留清空输入时立即移除高亮的行为。 --- .../src/components/DataGrid.layout.test.tsx | 6 ++- frontend/src/components/DataGrid.tsx | 27 +++++------- frontend/src/utils/dataGridFind.test.ts | 23 +++++++++- frontend/src/utils/dataGridFind.ts | 43 +++++++++++++++++-- 4 files changed, 76 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/DataGrid.layout.test.tsx b/frontend/src/components/DataGrid.layout.test.tsx index fd85f322..479c3d06 100644 --- a/frontend/src/components/DataGrid.layout.test.tsx +++ b/frontend/src/components/DataGrid.layout.test.tsx @@ -1118,8 +1118,10 @@ describe('DataGrid layout', () => { expect(pageFindSource).toContain("if (event.key === 'Escape')"); expect(pageFindSource).toContain('onCancel();'); expect(pageFindSource).toContain("textAlign: 'left'"); - expect(dataGridSource).toContain("const normalizedPageFindText = useMemo(() => normalizeDataGridFindQuery(pageFindText), [pageFindText]);"); - expect(dataGridSource).not.toContain("const normalizedPageFindText = useMemo(() => normalizeDataGridFindQuery(deferredPageFindText), [deferredPageFindText]);"); + expect(dataGridSource).toContain('const deferredPageFindText = useDeferredValue(pageFindText);'); + expect(dataGridSource).toMatch(/normalizeDataGridFindQuery\(pageFindText\)[\s\S]*?normalizeDataGridFindQuery\(deferredPageFindText\)[\s\S]*?: ''/); + expect(dataGridSource).toContain('collectDataGridFindResult('); + expect(dataGridSource).not.toContain('summarizeDataGridFindMatches('); expect(dataGridSource).toContain("if (event.key === 'Escape')"); expect(dataGridSource).toContain('if (activeSelection.size === 0) {'); expect(dataGridSource).toContain('closeCellEditMode();'); diff --git a/frontend/src/components/DataGrid.tsx b/frontend/src/components/DataGrid.tsx index c4892444..6419b9cb 100644 --- a/frontend/src/components/DataGrid.tsx +++ b/frontend/src/components/DataGrid.tsx @@ -104,13 +104,12 @@ import { } from '../utils/dataGridWhereFilter'; import { attachDataGridFindRenderVersion, - collectDataGridFindMatches, + collectDataGridFindResult, findDataGridTextRanges, hasDataGridFindRenderVersionChanged, normalizeDataGridFindQuery, resolveDataGridColumnQuickFindTarget, resolveDataGridFindNavigationIndex, - summarizeDataGridFindMatches, type DataGridFindMatch, type DataGridFindNavigationDirection, } from '../utils/dataGridFind'; @@ -428,8 +427,13 @@ const DataGrid: React.FC = ({ const [activePageFindMatchIndex, setActivePageFindMatchIndex] = useState(-1); const columnQuickFindHighlightTimerRef = useRef | null>(null); const deferredColumnQuickFindText = useDeferredValue(columnQuickFindText); - // 当前页查找需要即时反馈;否则清空输入框后高亮会继续停留一拍。 - const normalizedPageFindText = useMemo(() => normalizeDataGridFindQuery(pageFindText), [pageFindText]); + const deferredPageFindText = useDeferredValue(pageFindText); + // 大结果集查找属于低优先级渲染;清空仍立即生效,避免旧高亮残留一拍。 + const normalizedPageFindText = useMemo(() => ( + normalizeDataGridFindQuery(pageFindText) + ? normalizeDataGridFindQuery(deferredPageFindText) + : '' + ), [deferredPageFindText, pageFindText]); const normalizedColumnQuickFindText = useMemo( () => normalizeDataGridFindQuery(deferredColumnQuickFindText), [deferredColumnQuickFindText], @@ -2303,7 +2307,7 @@ const DataGrid: React.FC = ({ } }, [closeVirtualInlineEditor, currentConnConfig, dbType, form, handleCellSave, virtualEditingCell]); - const pageFindMatches = useMemo(() => collectDataGridFindMatches( + const pageFindResult = useMemo(() => collectDataGridFindResult( mergedDisplayData, displayColumnNames, normalizedPageFindText, @@ -2314,17 +2318,8 @@ const DataGrid: React.FC = ({ ), (row, rowIndex) => String(row?.[GONAVI_ROW_KEY] ?? `row-${rowIndex}`), ), [mergedDisplayData, displayColumnNames, normalizedPageFindText, columnMetaMap, columnMetaMapByLowerName, currentConnConfig]); - - const pageFindSummary = useMemo(() => summarizeDataGridFindMatches( - mergedDisplayData, - displayColumnNames, - normalizedPageFindText, - (value, _row, columnName) => formatCellDisplayText( - value, - (columnMetaMap[columnName] || columnMetaMapByLowerName[columnName.toLowerCase()])?.type, - currentConnConfig, - ), - ), [mergedDisplayData, displayColumnNames, normalizedPageFindText, columnMetaMap, columnMetaMapByLowerName, currentConnConfig]); + const pageFindMatches = pageFindResult.matches; + const pageFindSummary = pageFindResult.summary; useEffect(() => { setActivePageFindMatchIndex(-1); diff --git a/frontend/src/utils/dataGridFind.test.ts b/frontend/src/utils/dataGridFind.test.ts index 935837c3..3b82352b 100644 --- a/frontend/src/utils/dataGridFind.test.ts +++ b/frontend/src/utils/dataGridFind.test.ts @@ -1,8 +1,9 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { attachDataGridFindRenderVersion, collectDataGridFindMatches, + collectDataGridFindResult, findDataGridTextRanges, hasDataGridFindRenderVersionChanged, normalizeDataGridFindQuery, @@ -80,6 +81,26 @@ describe('dataGridFind', () => { ]); }); + it('collects matches and their summary with one cell scan', () => { + const rows = [ + { id: 1, name: 'Alpha alpha', note: 'beta' }, + { id: 2, name: 'none', note: 'Alpha' }, + ]; + const getCellText = vi.fn((value: unknown) => String(value ?? '')); + + const result = collectDataGridFindResult( + rows, + ['name', 'note'], + 'alpha', + getCellText, + (row) => String(row.id), + ); + + expect(getCellText).toHaveBeenCalledTimes(rows.length * 2); + expect(result.summary).toEqual({ matchedCellCount: 2, occurrenceCount: 3 }); + expect(result.matches).toHaveLength(3); + }); + it('resolves previous and next navigation indexes with wrapping', () => { expect(resolveDataGridFindNavigationIndex(-1, 4, 'next')).toBe(0); expect(resolveDataGridFindNavigationIndex(0, 4, 'next')).toBe(1); diff --git a/frontend/src/utils/dataGridFind.ts b/frontend/src/utils/dataGridFind.ts index f9a48fee..8b3ea4ba 100644 --- a/frontend/src/utils/dataGridFind.ts +++ b/frontend/src/utils/dataGridFind.ts @@ -8,6 +8,11 @@ export interface DataGridFindSummary { occurrenceCount: number; } +export interface DataGridFindResult { + matches: DataGridFindMatch[]; + summary: DataGridFindSummary; +} + export interface DataGridFindMatch extends DataGridTextRange { rowIndex: number; rowKey: string; @@ -105,17 +110,41 @@ export const collectDataGridFindMatches = ( query: string, getCellText: (value: unknown, row: T, columnName: string) => string, getRowKey: (row: T, rowIndex: number) => string, -): DataGridFindMatch[] => { +): DataGridFindMatch[] => collectDataGridFindResult( + rows, + columnNames, + query, + getCellText, + getRowKey, +).matches; + +export const collectDataGridFindResult = ( + rows: T[], + columnNames: string[], + query: string, + getCellText: (value: unknown, row: T, columnName: string) => string, + getRowKey: (row: T, rowIndex: number) => string, +): DataGridFindResult => { const normalizedQuery = normalizeDataGridFindQuery(query); - if (!normalizedQuery) return []; + if (!normalizedQuery) { + return { + matches: [], + summary: { matchedCellCount: 0, occurrenceCount: 0 }, + }; + } const matches: DataGridFindMatch[] = []; + let matchedCellCount = 0; rows.forEach((row, rowIndex) => { const record = row as Record; const rowKey = getRowKey(row, rowIndex); columnNames.forEach((columnName, columnIndex) => { - findDataGridTextRanges(getCellText(record[columnName], row, columnName), normalizedQuery).forEach((range, occurrenceIndex) => { + const ranges = findDataGridTextRanges(getCellText(record[columnName], row, columnName), normalizedQuery); + if (ranges.length > 0) { + matchedCellCount += 1; + } + ranges.forEach((range, occurrenceIndex) => { matches.push({ rowIndex, rowKey, @@ -129,7 +158,13 @@ export const collectDataGridFindMatches = ( }); }); - return matches; + return { + matches, + summary: { + matchedCellCount, + occurrenceCount: matches.length, + }, + }; }; export const resolveDataGridFindNavigationIndex = (