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

RTK Query中onQueryStarted触发两次致缓存更新异常

问题分析与解决方案

背景

我有getGroupList API,在3个页面传入不同参数调用,需要生成3个独立缓存条目。另有deleteGroupUser API用于移除群组用户,若使用invalidateTags关联getGroupList标签会触发3次API请求,因此采用onQueryStarted做乐观缓存更新。

问题

onQueryStarted会触发两次,导致group_users_count从10被减至8,无法达成减1的预期目标。

相关代码

API定义

getGroupList: builder.query<BaseListResponseFor<IGroup>, GroupsQueryParams>({
  query: (params) => ({ url: 'api/v1/groups', method: APIMethods.GET, params }),
  serializeQueryArgs: ({ queryArgs, endpointDefinition, endpointName }) => {
    const { user_id, role } = queryArgs;
    return defaultSerializeQueryArgs({ endpointName, queryArgs: { user_id, role }, endpointDefinition });
  },
  providesTags: (result) => tagsProvider<IGroup>(result, GROUPS_CACHE_TAGS.GroupList),
  transformResponse(response) { return response.data; },
  merge: (currentCache, responseData) => { ... }
}),
deleteGroupUser: builder.mutation<BaseDeleteResponse, { groupId?: number; groupUserId: number }>({
  query: ({ groupUserId }) => ({ url: `api/v1/group_users/${groupUserId}`, method: APIMethods.DELETE }),
  onQueryStarted: ({ groupUserId, groupId }, { dispatch, queryFulfilled }) => {
    const myGroupsPatch = dispatch(
      groupsApi.util.updateQueryData('getGroupList', { user_id: store?.getState()?.auth?.userId }, (draft) => {
        const index = draft.items.findIndex(group => group?.own_group_user?.id === groupUserId);
        if (index >= 0) draft.items.splice(index, 1);
      })
    );
    queryFulfilled.catch(myGroupsPatch.undo);
    const manageGroupsPatch = dispatch(
      groupsApi.util.updateQueryData('getGroupList', { role: GroupUserRole.Owner, user_id: store?.getState()?.auth?.userId }, (draft) => {
        const index = draft.items.findIndex(group => group?.own_group_user?.id === groupUserId);
        if (index >= 0) draft.items.splice(index, 1);
        const groupIndex = draft.items.findIndex(group => group?.id === groupId);
        if (groupIndex >= 0) draft.items[groupIndex].group_users_count -= 1; // 此处被执行两次
      })
    );
    queryFulfilled.catch(manageGroupsPatch.undo);
  },
  invalidatesTags: () => [GROUPS_CACHE_TAGS.Group, GROUPS_CACHE_TAGS.GroupUserList],
}),

API调用

// GroupsManageScreen.tsx
const { data, refetch } = useGetGroupListQuery({ page, user_id: userId, keyword: debouncedQuery, role: GroupUserRole.Owner });
// GroupsHomeScreen.tsx
const { data, refetch } = useGetGroupListQuery({ page, user_id: userId, keyword: debouncedQuery }, { refetchOnMountOrArgChange: true });
// GroupsDiscoverScreen.tsx
const { data, refetch } = useGetGroupListQuery({ page, user_id: userId, keyword: debouncedQuery, role: GroupUserRole.NotMember }, { refetchOnMountOrArgChange: true });
// GroupMembersScreen.tsx
const [deleteGroupUser] = useDeleteGroupUserMutation();
const onRemoveGroupUser = useCallback(async (userId: number) =>
  await deleteGroupUser({ groupUserId: userId, groupId: groupData?.id }).then(() => setToast({ render: () => <Toast text={'User removed'} display /> })),
[deleteGroupUser, groupData?.id, setToast]
);

预期目标

移除1个用户后,对应群组的group_users_count减1。

实际结果

group_users_count被减2,例如从10变为8。


解决步骤

1. 确认mutation是否被重复调用

先排查deleteGroupUser是否被触发两次:

  • 在onRemoveGroupUser和deleteGroupUser的query方法中添加日志,检查是否发送两次DELETE请求:
    // GroupMembersScreen.tsx
    const onRemoveGroupUser = useCallback(async (userId: number) => {
      console.log('触发删除:', userId);
      await deleteGroupUser({ groupUserId: userId, groupId: groupData?.id }).then(() => ...);
    }, [...]);
    
    // deleteGroupUser的query配置
    query: ({ groupUserId }) => {
      console.log('发送DELETE请求:', groupUserId);
      return { url: `api/v1/group_users/${groupUserId}`, method: APIMethods.DELETE };
    },
    
  • 若日志出现两次,说明函数被重复触发,需修正:
    • 检查按钮是否同时绑定onClick和onSubmit事件
    • 用useRef标记执行状态,避免重复调用:
      const isDeleting = useRef(false);
      const onRemoveGroupUser = useCallback(async (userId: number) => {
        if (isDeleting.current) return;
        isDeleting.current = true;
        try {
          await deleteGroupUser({ groupUserId: userId, groupId: groupData?.id });
          setToast({ render: () => <Toast text={'User removed'} display /> });
        } finally {
          isDeleting.current = false;
        }
      }, [deleteGroupUser, groupData?.id, setToast]);
      
    • 确保useCallback依赖项正确,避免不必要的回调重建导致重复绑定

2. 修正缓存键序列化逻辑

当前getGroupList的serializeQueryArgs忽略了page和keyword,会导致同一user_id+role下的不同查询共享缓存,updateQueryData会同时更新多个缓存条目。调整序列化逻辑,包含所有必要参数,确保每个页面缓存独立:

serializeQueryArgs: ({ queryArgs, endpointDefinition, endpointName }) => {
  const { user_id, role, page, keyword } = queryArgs;
  return defaultSerializeQueryArgs({ endpointName, queryArgs: { user_id, role, page, keyword }, endpointDefinition });
},

3. 优化乐观更新逻辑

调整操作顺序,先处理计数减少,再删除目标条目,避免索引变化导致的意外操作:

const manageGroupsPatch = dispatch(
  groupsApi.util.updateQueryData('getGroupList', { role: GroupUserRole.Owner, user_id: store?.getState()?.auth?.userId }, (draft) => {
    // 先更新用户计数
    const groupIndex = draft.items.findIndex(group => group?.id === groupId);
    if (groupIndex >= 0) {
      draft.items[groupIndex].group_users_count = Math.max(draft.items[groupIndex].group_users_count - 1, 0);
    }
    // 再删除用户所在的群组条目
    const index = draft.items.findIndex(group => group?.own_group_user?.id === groupUserId);
    if (index >= 0) draft.items.splice(index, 1);
  })
);

内容的提问来源于stack exchange,提问作者Brogrammer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 14:31:04