RTK Query:如何处理多参数端点的缓存更新或标签失效?
关于RTK Query无限滚动下多缓存项更新的解决方案
现有实现与问题
我用RTK Query实现了无限滚动,通过以下代码拼接新旧分页数据:
getJobs: builder.query< PaginatedResponse<JobType>, JobFilterParamType >({ query: (params) => ({ url: `${jobRequirement}/`, params, }), serializeQueryArgs: ({ queryArgs }) => { const newQueryArgs = { ...queryArgs }; if (newQueryArgs.page) { delete newQueryArgs.page; } return newQueryArgs; }, merge: (currentCache, newItems) => { // 合并新旧分页数据 if (currentCache.results) { return { ...currentCache, ...newItems, results: [...currentCache.results, ...newItems.results], }; } return newItems; }, providesTags: (data) => data ? [{ type: "Jobs", id: "LIST" }] : [{ type: "Jobs", id: "LIST" }], })
API返回格式如下:
{ count: 30; next: 'https://..../?page=2'; previous: null; results: [{...}, {...}, ...]; }
更新列表项时,直接失效getJobs端点会丢失除第1页外的所有数据,因此用以下代码处理项更新:
patchJob: builder.mutation< JobType, Partial<JobType> >({ query: ({id, ...data}) => ({ url: `${jobRequirement}/${id}`, method: "POST", body: data, }), async onQueryStarted(data, { dispatch, queryFulfilled }) { const filter = store.getState().jobFilter; // 存储筛选条件的slice const patchResult = dispatch( api.util.updateQueryData("getJobs", filter, (draft) => { const indexOfUpdatedJob = draft.results.findIndex((job) => job.id === data.job_requirement); draft.results[indexOfUpdatedJob] = data; // 更新对应项 }) ); try { await queryFulfilled; } catch { patchResult.undo(); } }, invalidatesTags: (result) => { return result ? [{ type: "Jobs", id: result.job_requirement }] : []; }, })
当前核心问题:存在不同参数的缓存(如/jobs?page=1和/jobs?page=1&skill=123)时,更新操作只会同步当前端点的缓存,不会更新其他包含该项的端点缓存;且直接失效getJobs会导致页面滚动跳转。
解决方案实现
方案1:更新所有参数变体中的对应项
通过api.util.selectInvalidatedBy获取所有关联目标Job项的缓存条目,逐个更新:
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) { const jobId = data.job_requirement; // 获取所有包含该Job的缓存查询 const matchingQueries = api.util.selectInvalidatedBy( getState(), [{ type: "Jobs", id: jobId }] ); // 遍历所有匹配的查询,更新对应缓存项 const undoPatches = matchingQueries.map(({ endpointName, originalArgs }) => { return dispatch( api.util.updateQueryData(endpointName, originalArgs, (draft) => { const jobIndex = draft.results.findIndex(job => job.id === jobId); if (jobIndex !== -1) { draft.results[jobIndex] = data; } }) ); }); try { await queryFulfilled; } catch { // 请求失败时撤销所有更新 undoPatches.forEach(undo => undo()); } }
同时需要调整getJobs的providesTags,为每个返回的Job项单独生成标签,确保selectInvalidatedBy能匹配到所有包含该Job的缓存:
providesTags: (data) => { if (!data) return [{ type: "Jobs", id: "LIST" }]; return [ { type: "Jobs", id: "LIST" }, ...data.results.map(job => ({ type: "Jobs", id: job.id } as const)) ]; }
方案2:失效除当前端点外的所有其他变体
先获取当前用户使用的查询参数,再遍历所有getJobs缓存条目,排除当前参数变体后失效其余缓存:
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) { const currentFilter = store.getState().jobFilter; // 获取所有getJobs的缓存条目 const allJobQueries = api.util.selectEndpoints(getState()).getJobs?.queries; if (allJobQueries) { // 过滤掉当前使用的参数变体(忽略page参数,与serializeQueryArgs逻辑对齐) const otherQueries = Object.entries(allJobQueries) .filter(([_, { originalArgs }]) => { const argsWithoutPage = { ...originalArgs }; delete argsWithoutPage.page; const currentFilterWithoutPage = { ...currentFilter }; delete currentFilterWithoutPage.page; return JSON.stringify(argsWithoutPage) !== JSON.stringify(currentFilterWithoutPage); }) .map(([queryKey]) => queryKey); // 失效这些非当前变体的缓存 otherQueries.forEach(queryKey => { dispatch(api.util.invalidateTags([{ type: "Jobs", id: queryKey }])); }); } // 保留原有的当前缓存更新逻辑 const patchResult = dispatch( api.util.updateQueryData("getJobs", currentFilter, (draft) => { const indexOfUpdatedJob = draft.results.findIndex((job) => job.id === data.job_requirement); draft.results[indexOfUpdatedJob] = data; }) ); try { await queryFulfilled; } catch { patchResult.undo(); } }
内容的提问来源于stack exchange,提问作者reddur
相关产品推荐
相关产品推荐

