React Query如何获取保留历史数据 解决滚动加载旧数据丢失问题
问题根因
当前代码配置的keepPreviousData: true仅会在新页码请求加载阶段临时保留上一页结果,避免页面出现加载空白。新页码请求成功后,当前query key [ACTIVITY_FEED, activityFeedPageIndex] 对应的返回值会直接替换为最新页数据。虽然不同页码的历史查询结果实际以独立缓存条目的形式存在于React Query缓存中(每个页码对应单独缓存),但未做跨页数据聚合时,渲染层只能拿到当前页内容,表现为旧数据丢失。
可行实现方案
方案1:使用官方useInfiniteQuery(无限滚动场景首选)
React Query原生提供了适配无限滚动/分页加载场景的useInfiniteQuery Hook,内置历史数据聚合、页码维护、加载状态标识能力,无需手动编写数据拼接逻辑。
实现示例:
const useActivityFeedData = (pageSize = Config.defaultPageSize) => useInfiniteQuery( [ACTIVITY_FEED], ({ pageParam = 1 }) => Api.user.getActivityFeedData(pageParam, pageSize), { initialPageParam: 1, // 定义下一页页码的计算规则 getNextPageParam: (lastPage, allPages) => { // 按实际接口逻辑调整:当前页数据量小于页大小则判定为没有下一页 return lastPage.activityList.length < pageSize ? undefined : allPages.length + 1; }, select: (res) => ({ // 拍平所有已加载页数据,得到包含全部历史内容的列表 activityList: res.pages.flatMap(page => page.activityList), }), } ); // 组件内调用 const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useActivityFeedData(); // 滚动触底回调 const handleReachBottom = () => { if (hasNextPage && !isFetchingNextPage) fetchNextPage(); }; // 直接渲染data.activityList即可拿到全量历史加载数据
该方案优势:
- 自动按页码缓存数据,回退到已加载页码时不会重复发请求
- 内置下一页判断、加载状态标识,无需额外维护页码状态
- 原生支持双向加载(可同时实现下拉加载更早历史、上拉拉取最新内容)
方案2:保留现有useQuery写法,手动聚合缓存数据
如果受业务逻辑限制无法替换为useInfiniteQuery,可以通过React Query提供的getQueryData方法,手动读取缓存中所有已加载页码的数据做聚合。
修改后的Hook代码:
const useActivityFeedData = ( activityFeedPageIndex: number, pageSize = Config.defaultPageSize, ) => { const queryClient = useQueryClient(); return useQuery( [ACTIVITY_FEED, activityFeedPageIndex], () => Api.user.getActivityFeedData(activityFeedPageIndex, pageSize), { keepPreviousData: true, select: (res: any) => { const allActivities = []; // 读取所有已加载页码的缓存数据 for (let page = 1; page <= activityFeedPageIndex; page++) { const cachedPage = queryClient.getQueryData([ACTIVITY_FEED, page]); if (cachedPage?.activityList) { allActivities.push(...cachedPage.activityList); } } // 补充当前页最新返回数据,避免缓存更新延迟导致的数据缺失 allActivities.push(...res.activityList); // 按业务唯一id去重,避免重复渲染 const uniqueActivityList = Array.from( new Map(allActivities.map(item => [item.id, item])).values() ); return { activityList: uniqueActivityList }; }, }, ); }
该方案需要自行维护页码累加、数据去重、是否存在下一页的判断逻辑,维护成本更高,仅推荐在特殊定制场景下使用。
避坑提示
- 不要将所有页数据手动拼接后存入固定query key的缓存中,会破坏React Query原生的缓存失效、自动重试、垃圾回收机制,容易引发数据不一致、内存泄漏问题
- 两种方案都支持通过
setQueryData精准更新单条缓存数据,无需在单条内容变更时全量重拉所有页数据
内容的提问来源于stack exchange,提问作者Fathe
相关产品推荐
相关产品推荐

