mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-11 01:03:51 +08:00
⚡️ perf(data-grid): 合并当前页查找扫描
将匹配坐标与命中统计合并为单次单元格遍历,避免重复格式化整页数据。 使用延迟值调度大结果集查找,同时保留清空输入时立即移除高亮的行为。
This commit is contained in:
@@ -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();');
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
Reference in New Issue
Block a user