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

React Query:如何确定哪个mutation触发了query的重新获取

解决React Query中按钮加载状态互相干扰的问题

针对你遇到的所有按钮共享useIsFetching状态导致加载状态混乱的问题,这里有几个实用的解决方案:

方案1:乐观更新+mutation自身状态(最推荐)

直接给每个mutation加上乐观更新逻辑,UI会即时响应操作,无需等待query重新获取完成,按钮的加载状态只依赖对应mutation的isLoading,自然不会互相干扰。

示例代码(删除操作):

const queryClient = useQueryClient()

const deleteTodoMutation = useMutation({
  mutationFn: (todoId) => api.deleteTodo(todoId),
  // 乐观更新:先修改本地缓存
  onMutate: async (todoId) => {
    // 取消当前正在进行的todos查询,避免覆盖乐观更新结果
    await queryClient.cancelQueries({ queryKey: ['todos'] })
    const previousTodos = queryClient.getQueryData(['todos'])
    
    // 从缓存中移除目标待办项
    queryClient.setQueryData(['todos'], old => 
      old.filter(todo => todo.id !== todoId)
    )
    
    // 返回回滚函数,用于mutation失败时恢复缓存
    return { previousTodos }
  },
  // mutation失败时回滚缓存
  onError: (err, todoId, context) => {
    queryClient.setQueryData(['todos'], context.previousTodos)
  },
  // 最终同步服务器数据(可选,确保缓存与后端一致)
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  }
})

删除按钮的状态控制:

<button 
  onClick={() => deleteTodoMutation.mutate(todo.id)}
  disabled={deleteTodoMutation.isLoading}
>
  {deleteTodoMutation.isLoading ? <Spinner /> : '删除'}
</button>

这种方式既解决了加载状态的问题,又提升了用户体验,因为UI会立刻响应操作。

方案2:给每个按钮维护独立的加载状态(适合不使用乐观更新的场景)

如果必须等待query重新获取完成才更新UI,可以给每个按钮单独维护一个本地状态,结合mutation和query的状态来控制:

示例代码(删除操作):

import { useState, useEffect } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'

const [isDeleteLoading, setIsDeleteLoading] = useState(false)
const queryClient = useQueryClient()

const deleteTodoMutation = useMutation({
  mutationFn: (todoId) => api.deleteTodo(todoId),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  }
})

const todosQuery = useQuery({
  queryKey: ['todos'],
  queryFn: api.getTodos,
  onSuccess: () => {
    // query获取完成后,关闭删除按钮的加载状态
    if (deleteTodoMutation.status === 'success') {
      setIsDeleteLoading(false)
      // 重置mutation状态,避免后续其他操作干扰
      deleteTodoMutation.reset()
    }
  }
})

const handleDelete = (todoId) => {
  setIsDeleteLoading(true)
  deleteTodoMutation.mutate(todoId)
}

按钮状态控制:

<button 
  onClick={() => handleDelete(todo.id)}
  disabled={isDeleteLoading || deleteTodoMutation.isLoading}
>
  {isDeleteLoading || deleteTodoMutation.isLoading ? <Spinner /> : '删除'}
</button>

创建、更新按钮同理,各自维护独立的isCreateLoading、isUpdateLoading状态即可。

方案3:useIsFetching结合mutation状态过滤

这种方式不需要额外的本地状态,通过判断mutation的状态来过滤query的fetch状态:

import { useQuery, useMutation, useQueryClient, useIsFetching } from '@tanstack/react-query'
import { useEffect } from 'react'

const queryClient = useQueryClient()
const todosQueryKey = ['todos']

const deleteTodoMutation = useMutation({
  mutationFn: (todoId) => api.deleteTodo(todoId),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: todosQueryKey })
  }
})

// 只监听todos列表的请求状态
const isTodosFetching = useIsFetching({ queryKey: todosQueryKey })

// 只有当前删除mutation执行中,或刚成功且query正在获取时,才显示加载
const isDeleteLoading = deleteTodoMutation.isLoading || (deleteTodoMutation.isSuccess && isTodosFetching > 0)

// query获取完成后重置mutation状态,避免后续操作干扰
useEffect(() => {
  if (isTodosFetching === 0 && deleteTodoMutation.isSuccess) {
    deleteTodoMutation.reset()
  }
}, [isTodosFetching, deleteTodoMutation])

按钮状态控制只需判断isDeleteLoading即可,逻辑和前两种方案一致。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 14:35:40