You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 17:40:06