perf(tabs): debounce UI state mementos

This commit is contained in:
t8y2 committed 2026-09-10 20:33:45 +08:00
1 parent 8f3afe0397
commit 6f6abf1d38
2 files changed
+28 -5

No files matched your search

+7 -2
View File
@@ -1,8 +1,8 @@
/** @vitest-environment happy-dom */
import { createApp, defineComponent, h, nextTick, ref } from "vue";
import { afterEach, describe, expect, it } from "vitest";
import { MAX_TAB_PAGE_UI_STATE_BYTES, provideTabUiState, sanitizeTabPageUiState, sanitizeTabUiState, useTabUiState } from "@/lib/tabs/tabUiState";
import { afterEach, describe, expect, it, vi } from "vitest";
import { MAX_TAB_PAGE_UI_STATE_BYTES, provideTabUiState, sanitizeTabPageUiState, sanitizeTabUiState, TAB_UI_STATE_TRACK_DEBOUNCE_MS, useTabUiState } from "@/lib/tabs/tabUiState";
const mounted: Array<{ unmount: () => void; host: HTMLElement }> = [];
@@ -11,10 +11,12 @@ afterEach(() => {
unmount();
host.remove();
}
vi.useRealTimers();
});
describe("tabUiState", () => {
it("restores a namespace and flushes its last value before unmount", async () => {
vi.useFakeTimers();
const updates: unknown[] = [];
const trackedValue = ref(0);
let initialValue = 0;
@@ -53,6 +55,9 @@ describe("tabUiState", () => {
trackedValue.value = 8;
await nextTick();
expect(updates).toHaveLength(0);
vi.advanceTimersByTime(TAB_UI_STATE_TRACK_DEBOUNCE_MS);
await nextTick();
expect(updates.at(-1)).toEqual({ Panel: { value: 8 } });
trackedValue.value = 9;
+21 -3
View File
@@ -2,6 +2,7 @@ import { inject, onBeforeUnmount, provide, watch, type InjectionKey } from "vue"
import type { TabPageUiState, TabUiState } from "@/types/database";
export const MAX_TAB_PAGE_UI_STATE_BYTES = 64 * 1024;
export const TAB_UI_STATE_TRACK_DEBOUNCE_MS = 100;
interface TabUiStateContext {
snapshot: TabPageUiState;
@@ -28,11 +29,28 @@ export function useTabUiState<T extends object>(
const update = (patch: Partial<T>) => {
if (active) context?.update(namespace ? { [namespace]: patch } : (patch as TabPageUiState));
};
const track = (snapshot: () => Partial<T>) => {
const stop = watch(snapshot, update, { deep: true, flush: "post" });
onBeforeUnmount(() => {
const track = (snapshot: () => Partial<T>, debounceMs = TAB_UI_STATE_TRACK_DEBOUNCE_MS) => {
let timer: ReturnType<typeof setTimeout> | undefined;
const flush = () => {
if (timer !== undefined) {
clearTimeout(timer);
timer = undefined;
}
update(snapshot());
};
const schedule = () => {
if (!active) return;
if (timer !== undefined) clearTimeout(timer);
if (debounceMs <= 0) {
flush();
return;
}
timer = setTimeout(flush, debounceMs);
};
const stop = watch(snapshot, schedule, { deep: true, flush: "post" });
onBeforeUnmount(() => {
stop();
flush();
active = false;
});
};