RTK Query实现无限滚动如何合并新旧数据且保留数据失效机制
RTK Query 无限滚动场景新旧数据合并官方实现方案
RTK Query 原生支持无限滚动/分页加载场景的新旧数据合并需求,禁止在组件层手动拼接新旧数据,这种做法脱离缓存层管理,确实会导致缓存失效、自动更新能力失效,官方推荐通过 endpoint 内置的三个配置项在缓存层完成合并,完整保留所有原生能力。
核心实现配置
在定义对应 API endpoint 时,添加以下三个配置项即可:
getPosts: builder.query({ // 基础请求逻辑,首次拉取传page=1,后续滚动到底部传入累加的page值 query: (page = 1) => `/posts?page=${page}&pageSize=5`, // 1. 序列化缓存key:让所有分页请求归属到同一个缓存条目下 // 如果列表有筛选、排序参数,需要把这部分参数加入key,避免不同筛选条件的列表数据混淆 serializeQueryArgs: ({ endpointName, queryArgs }) => { return `${endpointName}-${queryArgs.sortBy ?? 'default'}-${queryArgs.tag ?? 'all'}` }, // 2. 缓存层合并逻辑:这是合并新旧数据的核心位置,运行在RTKQ内部缓存层,不会破坏自动更新能力 merge: (currentCache, responseData) => { // 先做ID去重,避免快速滚动重复请求导致列表出现重复项 const existingPostIds = new Set(currentCache.list.map(post => post.id)) const newUniquePosts = responseData.list.filter(post => !existingPostIds.has(post.id)) // 追加新数据到现有缓存 currentCache.list.push(...newUniquePosts) // 更新分页元信息,比如是否还有下一页、当前最新页码 currentCache.hasMore = responseData.hasMore currentCache.latestPage = responseData.currentPage }, // 3. 强制重拉判断:避免相同参数重复请求,同时保证新页码请求正常发起 forceRefetch: ({ currentArg, previousArg }) => { return currentArg !== previousArg }, // 正常配置标签,用于后续缓存失效 providesTags: (result) => { return [ { type: 'Post', id: 'LIST' }, ...(result?.list ?? []).map(post => ({ type: 'Post', id: post.id })) ] } })
组件层使用方式
组件层不需要做任何数据拼接,正常维护页码状态、调用hook即可,返回的data已经是合并完成的全量列表数据:
import { useState } from 'react' import { useGetPostsQuery } from './api/posts' export default function PostList() { const [page, setPage] = useState(1) // 直接取合并后的数据,所有缓存更新、自动刷新能力完全正常 const { data, isFetching } = useGetPostsQuery(page) const handleScroll = (e) => { const { scrollTop, clientHeight, scrollHeight } = e.target // 滚动到底部且还有更多数据、不在加载中时,页码+1触发下一页拉取 if ( scrollTop + clientHeight >= scrollHeight - 100 && !isFetching && data?.hasMore ) { setPage(prev => prev + 1) } } return ( <div onScroll={handleScroll} style={{ height: '80vh', overflow: 'auto' }}> {data?.list.map(post => ( <div key={post.id} className="post-item"> <h3>{post.title}</h3> <p>{post.content}</p> </div> ))} {isFetching && <div>加载中...</div>} </div> ) }
方案优势
- 完全保留RTK Query原生能力:单条Post数据变更、主动调用
invalidateTags失效缓存时,合并后的列表会自动同步更新,不会出现本地拼接数据不刷新的问题 - 自动避免重复请求:相同页码的请求不会重复发起
- 缓存独立:不同筛选、排序条件的列表会生成独立缓存,不会互相污染
- 不需要在组件层维护额外的列表状态,逻辑更简洁
注意事项
merge函数内请直接在传入的currentCache对象上做追加修改,不要整体替换currentCache的引用,否则会破坏响应式追踪- 必须加ID去重逻辑,防止快速滚动触发重复请求导致列表重复
- 如果需要重置列表(比如切换筛选条件、手动刷新),直接重置page状态为1即可,RTK Query会自动清空对应缓存重新拉取首屏数据
内容的提问来源于stack exchange,提问作者KwehDev
相关产品推荐
相关产品推荐

