From 2e50b301a32a8b82d22bb9bfedeccfee191c0ded Mon Sep 17 00:00:00 2001 From: TerryM Date: Sat, 30 May 2026 02:44:53 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E7=A9=BA=E9=97=B2=E9=A2=84=E7=83=AD?= =?UTF-8?q?=E5=85=A8=E9=83=A8=E8=B5=84=E6=96=99/=E7=83=AD=E9=97=A8?= =?UTF-8?q?=E8=B5=84=E6=96=99,=E7=82=B9=E5=87=BB=E5=89=8D=E5=B7=B2?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit usePostStream 导出 prefetchPostStream(已缓存/mock 则跳过)。PublicLayout 在 requestIdleCallback 空闲时后台预取「全部资料」「热门资料」首页数据并写入缓存, 用户点击进入时直接读缓存秒显,不再进页面才开始加载。预取仅 JSON,不拉图片。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../messageStream/hooks/usePostStream.ts | 12 +++++++++ src/layouts/PublicLayout.tsx | 26 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/components/messageStream/hooks/usePostStream.ts b/src/components/messageStream/hooks/usePostStream.ts index 907a1b5..5a12617 100644 --- a/src/components/messageStream/hooks/usePostStream.ts +++ b/src/components/messageStream/hooks/usePostStream.ts @@ -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(url)) return; + getJSON(url).catch(() => {}); +} + export function usePostStream(params: PostStreamParams): PostStreamResult { const [items, setItems] = useState([]); const [hasMore, setHasMore] = useState(true); diff --git a/src/layouts/PublicLayout.tsx b/src/layouts/PublicLayout.tsx index c49646f..2eff651 100644 --- a/src/layouts/PublicLayout.tsx +++ b/src/layouts/PublicLayout.tsx @@ -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 = () => {