fix(grid): avoid repeated search text allocations

This commit is contained in:
vrustx authored and GitHub committed 2026-07-18 21:40:08 +08:00
1 parent 9020a4dd51
commit bd7065f244
6 files changed
+134 -12

No files matched your search

@@ -25,6 +25,13 @@ export interface CanvasEditingCell {
col: number;
}
/** 搜索匹配的数值 key:列头匹配 displayRow 为 -1。相比字符串拼接 key,
* 每次按键构建 matchSet、每帧对可见单元格查询都零字符串分配。
* ponytail: 列数上限 65536,网格列数远达不到 */
export function dataGridSearchMatchKey(displayRow: number, col: number): number {
return (displayRow + 1) * 65536 + col;
}
export interface CanvasSearchMatch {
kind: "cell" | "column";
displayRow: number;
@@ -50,7 +57,7 @@ export interface DrawCanvasDataGridOptions {
hoverCell: CanvasHoverCell | null;
isScrolling: boolean;
editingCell: CanvasEditingCell | null;
searchMatchKeys: ReadonlySet<string>;
searchMatchKeys: ReadonlySet<number>;
currentSearchMatch: CanvasSearchMatch | null;
formatCell: (value: CellValue, columnIndex: number) => string;
draftCellPlaceholder?: string;
@@ -334,7 +341,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
const isDirtyCell = item.isDirtyCol[actualColIdx];
const selectedFillVisual = rowSelectionVisual || selectedCell;
const selectedBorderVisual = rowSelectionVisual || selectedCell;
const isSearchMatch = paintSearchMatches && searchMatchKeys.has(`cell:${item.displayIndex}:${actualColIdx}`);
const isSearchMatch = paintSearchMatches && searchMatchKeys.has(dataGridSearchMatchKey(item.displayIndex, actualColIdx));
const isCurrentSearchMatch = paintSearchMatches && currentSearchMatch?.displayRow === item.displayIndex && currentSearchMatch.col === actualColIdx;
const clippedX = Math.max(drawX, rowNumberWidth);
const cellPaintWidth = Math.min(width, drawX + colWidth) - clippedX;
@@ -0,0 +1,38 @@
import type { CellValue } from "@/lib/dataGrid/cellValue";
type RowLowerTextEntry = {
/** 每列缓存时的源值引用:单元格被原地编辑后引用不再相等,仅重算该格 */
values: CellValue[];
lowers: (string | undefined)[];
};
/**
* 搜索用小写文本缓存,按行数组身份挂 WeakMap:
* - 工作集天然等于当前在内存中的结果行(行数组是 markRaw 的稳定引用),
* 不存在固定容量 LRU 在全量顺序扫描下的零命中抖动,GC 自动回收旧结果;
* - 展示格式化器/列类型变化时由调用方 clear() 整体失效。
*/
export function createRowLowerTextCache(format: (value: CellValue, columnIndex: number) => string) {
let cache = new WeakMap<object, RowLowerTextEntry>();
function get(rowData: CellValue[], columnIndex: number): string {
let entry = cache.get(rowData);
if (!entry) {
entry = { values: [], lowers: [] };
cache.set(rowData, entry);
}
const value = rowData[columnIndex];
const cached = entry.lowers[columnIndex];
if (cached !== undefined && Object.is(entry.values[columnIndex], value)) return cached;
const lower = format(value, columnIndex).toLowerCase();
entry.values[columnIndex] = value;
entry.lowers[columnIndex] = lower;
return lower;
}
function clear() {
cache = new WeakMap<object, RowLowerTextEntry>();
}
return { get, clear };
}