Files
jiang13-forum/frontend/src/store/homeStore.ts
freefire 132ca5c82c fix: 用 Zustand 缓存 Feed 列表,返回时恢复滚动且不重复请求
点击 Logo 仍强制刷新;后退忽略 history 上的 refreshFeed,并支持过期静默刷新与下拉刷新。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 01:50:33 +08:00

107 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { create } from 'zustand';
import type { PostItem } from '../api/types';
import type { FeedSort } from '../components/FeedSortBar';
/** 列表缓存过期时间:超过后返回首页时静默刷新,仍先展示旧数据 */
export const FEED_STALE_MS = 5 * 60 * 1000;
/** 单条 Feed 筛选对应的缓存快照 */
export type FeedCacheEntry = {
posts: PostItem[];
postTotal: number;
page: number;
scrollTop: number;
/** 上次成功拉取 API 的时间戳ms */
lastFetchTime: number;
};
type FeedKeyParts = {
boardId: number;
keyword: string;
sort: FeedSort;
tag?: string;
author?: string;
titleOnly?: boolean;
};
/** 与筛选条件一一对应的缓存键 */
export function feedCacheKey(parts: FeedKeyParts): string {
const {
boardId,
keyword,
sort,
tag = '',
author = '',
titleOnly = false,
} = parts;
return `${boardId}:${keyword}:${tag}:${author}:${titleOnly ? 1 : 0}:${sort}`;
}
type HomeStoreState = {
/** 按筛选键存放多份列表,首页 / 板块 / 搜索互不覆盖 */
feeds: Record<string, FeedCacheEntry>;
getFeed: (key: string) => FeedCacheEntry | null;
setFeed: (key: string, data: Omit<FeedCacheEntry, 'lastFetchTime'> & { lastFetchTime?: number }) => void;
/** 滚动时只更新 scrollTop不改动列表数据 */
patchScroll: (key: string, scrollTop: number) => void;
isStale: (key: string, now?: number) => boolean;
clearFeed: (key: string) => void;
clearAll: () => void;
};
export const useHomeStore = create<HomeStoreState>((set, get) => ({
feeds: {},
getFeed: (key) => get().feeds[key] ?? null,
setFeed: (key, data) => {
set((state) => ({
feeds: {
...state.feeds,
[key]: {
posts: data.posts,
postTotal: data.postTotal,
page: data.page,
scrollTop: data.scrollTop,
lastFetchTime: data.lastFetchTime ?? Date.now(),
},
},
}));
},
patchScroll: (key, scrollTop) => {
const prev = get().feeds[key];
if (!prev) return;
// 数值未变则跳过,减少无意义的 store 更新
if (prev.scrollTop === scrollTop) return;
set((state) => ({
feeds: {
...state.feeds,
[key]: { ...prev, scrollTop },
},
}));
},
isStale: (key, now = Date.now()) => {
const entry = get().feeds[key];
if (!entry) return true;
return now - entry.lastFetchTime > FEED_STALE_MS;
},
clearFeed: (key) => {
set((state) => {
if (!(key in state.feeds)) return state;
const next = { ...state.feeds };
delete next[key];
return { feeds: next };
});
},
clearAll: () => set({ feeds: {} }),
}));
/** 非 React 路径(如 feedCache 封装)直接读写 */
export function getHomeStoreState() {
return useHomeStore.getState();
}