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
相关产品推荐
相关产品推荐

