perf: 资料流会话级缓存,切换页面不再重载

usePostStream 增加按筛选参数为 key 的内存缓存,保存已加载的全部内容与分页
游标。再次进入同一视图(如首页⇄全部资料来回切)时直接还原,不重置、不重新
请求、不显示骨架屏。整页刷新清空缓存以获取最新数据。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
TerryM
2026-05-30 02:04:26 +08:00
parent 6d62aad8c4
commit ae14b33f83

View File

@@ -94,6 +94,24 @@ function buildRealUrl(params: PostStreamParams, cursor?: string): string {
return `${q ? "/api/posts/search" : "/api/posts"}?${sp.toString()}`;
}
type CachedStream = {
items: Post[];
cursor: string | undefined;
hasMore: boolean;
};
/**
* Session cache of loaded stream state, keyed by the request params. Lets a
* view that's been seen before (e.g. switching Home ⇄ All) restore instantly —
* all loaded pages, no skeleton, no refetch — instead of reloading from page 1.
* In-memory only: a full page reload starts fresh.
*/
const streamCache = new Map<string, CachedStream>();
function streamKey(params: PostStreamParams): string {
return buildRealUrl(params);
}
export function usePostStream(params: PostStreamParams): PostStreamResult {
const [items, setItems] = useState<Post[]>([]);
const [hasMore, setHasMore] = useState(true);
@@ -172,6 +190,17 @@ export function usePostStream(params: PostStreamParams): PostStreamResult {
);
useEffect(() => {
// Restore a previously-loaded view instantly (no reset, no refetch).
const cached = streamCache.get(streamKey(params));
if (cached) {
setItems(cached.items);
cursorRef.current = cached.cursor;
setHasMore(cached.hasMore);
hasMoreRef.current = cached.hasMore;
setIsLoading(false);
setError(null);
return;
}
setItems([]);
cursorRef.current = undefined;
setHasMore(true);
@@ -188,6 +217,17 @@ export function usePostStream(params: PostStreamParams): PostStreamResult {
params.lang,
]);
// Persist loaded state so returning to this view restores it from cache.
useEffect(() => {
if (items.length === 0) return;
streamCache.set(streamKey(params), {
items,
cursor: cursorRef.current,
hasMore,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [items, hasMore]);
const loadMore = useCallback(() => {
fetchPage(false);
}, [fetchPage]);