️ perf(data-grid): 合并当前页查找扫描

将匹配坐标与命中统计合并为单次单元格遍历,避免重复格式化整页数据。

使用延迟值调度大结果集查找,同时保留清空输入时立即移除高亮的行为。
This commit is contained in:
Syngnat
2026-07-22 00:20:17 +08:00
parent c0b1b0873e
commit 76e74a02fd
4 changed files with 76 additions and 23 deletions

View File

@@ -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();');

View File

@@ -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<DataGridProps> = ({
const [activePageFindMatchIndex, setActivePageFindMatchIndex] = useState(-1);
const columnQuickFindHighlightTimerRef = useRef<ReturnType<typeof setTimeout> | 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<DataGridProps> = ({
}
}, [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<DataGridProps> = ({
),
(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);

View File

@@ -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);

View File

@@ -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 = <T>(
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 = <T>(
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<string, unknown>;
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 = <T>(
});
});
return matches;
return {
matches,
summary: {
matchedCellCount,
occurrenceCount: matches.length,
},
};
};
export const resolveDataGridFindNavigationIndex = (