perf: 空闲预热全部资料/热门资料,点击前已缓存

usePostStream 导出 prefetchPostStream(已缓存/mock 则跳过)。PublicLayout 在
requestIdleCallback 空闲时后台预取「全部资料」「热门资料」首页数据并写入缓存,
用户点击进入时直接读缓存秒显,不再进页面才开始加载。预取仅 JSON,不拉图片。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
TerryM
2026-05-30 02:44:53 +08:00
parent 4a20d80f68
commit 2e50b301a3
2 changed files with 38 additions and 0 deletions

View File

@@ -112,6 +112,18 @@ function streamKey(params: PostStreamParams): string {
return buildRealUrl(params);
}
/**
* Warm the cache for a stream view before the user navigates to it, so opening
* the page shows content immediately instead of starting to load on arrival.
* No-op for the mock backend or when the first page is already cached.
*/
export function prefetchPostStream(params: PostStreamParams): void {
if (USE_MOCK) return;
const url = buildRealUrl(params);
if (readJSONCache<PostListResponse>(url)) return;
getJSON<PostListResponse>(url).catch(() => {});
}
export function usePostStream(params: PostStreamParams): PostStreamResult {
const [items, setItems] = useState<Post[]>([]);
const [hasMore, setHasMore] = useState(true);

View File

@@ -5,6 +5,7 @@ import { Link, useLocation, useNavigate, useOutlet } from "react-router-dom";
import { pageTransition } from "../motion";
import { ArkLogoMark } from "../components/ArkLogoMark";
import { usePageTitle } from "../components/PageTitleContext";
import { prefetchPostStream } from "../components/messageStream/hooks/usePostStream";
import { BackToTop } from "../components/BackToTop";
import { DocumentMeta } from "../components/DocumentMeta";
import { SearchPanel } from "../components/SearchPanel";
@@ -292,6 +293,31 @@ export function PublicLayout() {
const footerInContentFlow = pathname === "/browse";
// Current page name shown in the header brand slot (falls back to the brand).
const pageTitle = usePageTitle();
// Warm "全部资料" and "热门资料" in the background (when idle) so tapping them
// shows content immediately instead of loading on arrival.
useEffect(() => {
const run = () => {
const base = {
scope: { kind: "all" as const },
type: "all",
q: "",
lang,
};
prefetchPostStream({ ...base, sort: "" });
prefetchPostStream({ ...base, sort: "popular" });
};
const ric = window as typeof window & {
requestIdleCallback?: (cb: () => void) => number;
cancelIdleCallback?: (id: number) => void;
};
if (ric.requestIdleCallback) {
const id = ric.requestIdleCallback(run);
return () => ric.cancelIdleCallback?.(id);
}
const id = window.setTimeout(run, 600);
return () => window.clearTimeout(id);
}, [lang]);
const popularHref = "/browse?sort=popular";
const goSearch = () => {