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

@@ -5,19 +5,34 @@ import { useDataGridSearch } from "@/composables/useDataGridSearch";
describe("useDataGridSearch", () => {
it("debounces matching across columns and cells", async () => {
vi.useFakeTimers();
const search = useDataGridSearch({ columns: ["id", "name"], rows: [[1, "Alice"]], getCellText: (row, column) => String(row[column] ?? "") });
// getCellSearchText 契约:返回小写文本(调用方负责缓存小写副本)
const search = useDataGridSearch({ columns: ["id", "name"], rows: [[1, "Alice"]], getCellSearchText: (row, column) => String(row[column] ?? "").toLowerCase() });
search.searchText.value = "ali";
await nextTick();
expect(search.matches.value).toEqual([]);
vi.advanceTimersByTime(150);
await nextTick();
expect(search.matches.value).toEqual([{ kind: "cell", displayRow: 0, col: 1 }]);
// matchSet 用数值 key:(displayRow+1)*65536+col
expect(search.matchSet.value.has((0 + 1) * 65536 + 1)).toBe(true);
vi.useRealTimers();
});
it("keys column-name matches with displayRow -1", async () => {
vi.useFakeTimers();
const search = useDataGridSearch({ columns: ["id", "name"], rows: [], getCellSearchText: () => "" });
search.searchText.value = "nam";
await nextTick();
vi.advanceTimersByTime(150);
await nextTick();
expect(search.matches.value).toEqual([{ kind: "column", displayRow: -1, col: 1 }]);
expect(search.matchSet.value.has((-1 + 1) * 65536 + 1)).toBe(true);
vi.useRealTimers();
});
it("suggests columns and replaces only the active token", async () => {
const columns = ref(["customer_id", "created_at"]);
const search = useDataGridSearch({ columns, rows: [], getCellText: () => "" });
const search = useDataGridSearch({ columns, rows: [], getCellSearchText: () => "" });
search.searchText.value = "status = cus";
await nextTick();
expect(search.suggestions.value).toEqual(["customer_id"]);
@@ -1,4 +1,5 @@
import { computed, getCurrentScope, nextTick, onScopeDispose, ref, toValue, watch, type MaybeRefOrGetter } from "vue";
import { dataGridSearchMatchKey } from "@/lib/dataGrid/canvasDataGridRenderer";
export type DataGridSearchMatch = {
kind: "cell" | "column";
@@ -10,7 +11,9 @@ export type UseDataGridSearchOptions<Row> = {
columns: MaybeRefOrGetter<readonly string[]>;
suggestionColumns?: MaybeRefOrGetter<readonly string[]>;
rows: MaybeRefOrGetter<readonly Row[]>;
getCellText: (row: Row, columnIndex: number) => string;
/** 必须返回小写文本(查询词已小写)。调用方可据此缓存小写副本,
* 避免每次按键对全部单元格重新分配 toLowerCase 字符串。 */
getCellSearchText: (row: Row, columnIndex: number) => string;
debounceMs?: number;
onNavigate?: (match: DataGridSearchMatch) => void;
};
@@ -38,12 +41,13 @@ export function useDataGridSearch<Row>(options: UseDataGridSearchOptions<Row>) {
});
toValue(options.rows).forEach((row, displayRow) => {
columns.forEach((_, col) => {
if (options.getCellText(row, col).toLowerCase().includes(query)) result.push({ kind: "cell", displayRow, col });
if (options.getCellSearchText(row, col).includes(query)) result.push({ kind: "cell", displayRow, col });
});
});
return result;
});
const matchSet = computed(() => new Set(matches.value.map((match) => `${match.kind}:${match.displayRow}:${match.col}`)));
// 数值 key(列头匹配 displayRow=-1),避免每匹配一次字符串拼接
const matchSet = computed(() => new Set(matches.value.map((match) => dataGridSearchMatchKey(match.displayRow, match.col))));
const currentMatch = computed(() => matches.value[currentMatchIndex.value] ?? null);
function clearTimer() {