perf(replies): progressive render, 500 replies/page, content-visibility

Set repliesPerPage to 500 to avoid Virtuoso pagination sort drift. Add useProgressiveRender with startTransition batching and CSS content-visibility for long reply lists.
This commit is contained in:
plebeius
2026-02-15 18:46:49 +08:00
parent ec620238e3
commit dedf18e199
5 changed files with 90 additions and 2 deletions
+62
View File
@@ -0,0 +1,62 @@
import { startTransition, useEffect, useState } from 'react';
interface UseProgressiveRenderOptions {
batchSize?: number;
intervalMs?: number;
resetKey?: string;
disabled?: boolean;
}
const DEFAULT_BATCH_SIZE = 50;
const DEFAULT_INTERVAL_MS = 100;
/**
* Progressively reveals items in batches via startTransition to avoid blocking the UI
* when mounting many heavy components (e.g. 500 reply components).
* When disabled or once fully caught up, returns the full array immediately.
*/
const useProgressiveRender = <T>(items: T[], options: UseProgressiveRenderOptions = {}): T[] => {
const { batchSize = DEFAULT_BATCH_SIZE, intervalMs = DEFAULT_INTERVAL_MS, resetKey, disabled = false } = options;
const [progressState, setProgressState] = useState<{ key: string | undefined; visibleCount: number }>({
key: resetKey,
visibleCount: batchSize,
});
const isResetKeyChanged = progressState.key !== resetKey;
const visibleCount = isResetKeyChanged ? batchSize : progressState.visibleCount;
useEffect(() => {
if (disabled || items.length <= batchSize) {
return;
}
const current = visibleCount >= items.length ? items.length : visibleCount;
if (current >= items.length) {
return;
}
const next = Math.min(current + batchSize, items.length);
const timer = window.setTimeout(() => {
startTransition(() => {
setProgressState({
key: resetKey,
visibleCount: next,
});
});
}, intervalMs);
return () => window.clearTimeout(timer);
}, [items.length, visibleCount, batchSize, intervalMs, disabled, resetKey]);
if (disabled || items.length <= batchSize) {
return items;
}
if (visibleCount >= items.length) {
return items;
}
return items.slice(0, visibleCount);
};
export default useProgressiveRender;