perf: 空闲预热全部资料/热门资料,点击前已缓存
usePostStream 导出 prefetchPostStream(已缓存/mock 则跳过)。PublicLayout 在 requestIdleCallback 空闲时后台预取「全部资料」「热门资料」首页数据并写入缓存, 用户点击进入时直接读缓存秒显,不再进页面才开始加载。预取仅 JSON,不拉图片。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,18 @@ function streamKey(params: PostStreamParams): string {
|
|||||||
return buildRealUrl(params);
|
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 {
|
export function usePostStream(params: PostStreamParams): PostStreamResult {
|
||||||
const [items, setItems] = useState<Post[]>([]);
|
const [items, setItems] = useState<Post[]>([]);
|
||||||
const [hasMore, setHasMore] = useState(true);
|
const [hasMore, setHasMore] = useState(true);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Link, useLocation, useNavigate, useOutlet } from "react-router-dom";
|
|||||||
import { pageTransition } from "../motion";
|
import { pageTransition } from "../motion";
|
||||||
import { ArkLogoMark } from "../components/ArkLogoMark";
|
import { ArkLogoMark } from "../components/ArkLogoMark";
|
||||||
import { usePageTitle } from "../components/PageTitleContext";
|
import { usePageTitle } from "../components/PageTitleContext";
|
||||||
|
import { prefetchPostStream } from "../components/messageStream/hooks/usePostStream";
|
||||||
import { BackToTop } from "../components/BackToTop";
|
import { BackToTop } from "../components/BackToTop";
|
||||||
import { DocumentMeta } from "../components/DocumentMeta";
|
import { DocumentMeta } from "../components/DocumentMeta";
|
||||||
import { SearchPanel } from "../components/SearchPanel";
|
import { SearchPanel } from "../components/SearchPanel";
|
||||||
@@ -292,6 +293,31 @@ export function PublicLayout() {
|
|||||||
const footerInContentFlow = pathname === "/browse";
|
const footerInContentFlow = pathname === "/browse";
|
||||||
// Current page name shown in the header brand slot (falls back to the brand).
|
// Current page name shown in the header brand slot (falls back to the brand).
|
||||||
const pageTitle = usePageTitle();
|
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 popularHref = "/browse?sort=popular";
|
||||||
|
|
||||||
const goSearch = () => {
|
const goSearch = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user