fix: 用 Zustand 缓存 Feed 列表,返回时恢复滚动且不重复请求

点击 Logo 仍强制刷新;后退忽略 history 上的 refreshFeed,并支持过期静默刷新与下拉刷新。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-01 01:50:33 +08:00
parent 185649db7c
commit 132ca5c82c
8 changed files with 384 additions and 92 deletions

View File

@@ -1,10 +1,16 @@
import type { NavigateFunction } from 'react-router-dom';
import type { PostItem } from '../api/types';
import type { FeedSort } from '../components/FeedSortBar';
import {
feedCacheKey,
getHomeStoreState,
type FeedCacheEntry,
} from '../store/homeStore';
/** 导航到帖子列表时附带的状态,用于同 URL 重复点击时强制刷新 */
export type FeedNavState = { refreshFeed?: boolean };
/** 对外暴露的缓存形状(兼容旧调用方;不含 lastFetchTime */
export type FeedCache = {
posts: PostItem[];
postTotal: number;
@@ -12,18 +18,13 @@ export type FeedCache = {
scrollTop: number;
};
/** 仅存内存SPA 内返回可恢复,浏览器刷新自动清空 */
const store = new Map<string, FeedCache>();
function cacheKey(
boardId: number,
keyword: string,
sort: FeedSort,
tag = '',
author = '',
titleOnly = false,
) {
return `${boardId}:${keyword}:${tag}:${author}:${titleOnly ? 1 : 0}:${sort}`;
function toPublic(entry: FeedCacheEntry): FeedCache {
return {
posts: entry.posts,
postTotal: entry.postTotal,
page: entry.page,
scrollTop: entry.scrollTop,
};
}
/** 读取帖子列表缓存(从详情页返回时恢复浏览位置) */
@@ -35,10 +36,12 @@ export function getFeedCache(
author = '',
titleOnly = false,
): FeedCache | null {
return store.get(cacheKey(boardId, keyword, sort, tag, author, titleOnly)) ?? null;
const key = feedCacheKey({ boardId, keyword, sort, tag, author, titleOnly });
const entry = getHomeStoreState().getFeed(key);
return entry ? toPublic(entry) : null;
}
/** 保存帖子列表缓存 */
/** 保存帖子列表缓存(写入 Zustand并记录拉取时间 */
export function setFeedCache(
boardId: number,
keyword: string,
@@ -48,20 +51,44 @@ export function setFeedCache(
author = '',
titleOnly = false,
) {
store.set(cacheKey(boardId, keyword, sort, tag, author, titleOnly), data);
const key = feedCacheKey({ boardId, keyword, sort, tag, author, titleOnly });
getHomeStoreState().setFeed(key, data);
}
/** 清除所有帖子列表缓存 */
export function clearAllFeedCache() {
store.clear();
getHomeStoreState().clearAll();
}
/** 主动刷新帖子列表时派发,用于同页内立即回到顶部 */
export const FEED_RESET_EVENT = 'feed-reset';
/** 清除缓存并导航到帖子列表(重复点击同一入口时也会刷新) */
/** 手机下拉刷新Feed 页):强制重拉列表并重置滚动,不整页 reload */
export const FEED_PULL_REFRESH_EVENT = 'feed-pull-refresh';
/** 当前浏览器地址是否已是目标 Feed URL忽略 hash */
function isSameFeedUrl(url: string): boolean {
try {
const target = new URL(url, window.location.origin);
return (
window.location.pathname === target.pathname
&& window.location.search === target.search
);
} catch {
return false;
}
}
/**
* 清除缓存并导航到帖子列表。
* 已在目标 URL如首页再点 Logo时额外派发强制重拉不依赖 RR 是否换 key。
*/
export function navigateFeed(nav: NavigateFunction, url: string) {
clearAllFeedCache();
window.dispatchEvent(new Event(FEED_RESET_EVENT));
// 同 URL 再点(典型:左上角 Logo必须立刻重拉否则可能只清缓存、界面仍显示旧列表
if (isSameFeedUrl(url)) {
window.dispatchEvent(new Event(FEED_PULL_REFRESH_EVENT));
}
nav(url, { state: { refreshFeed: true } satisfies FeedNavState });
}