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

offlineFirst模式下refetchOnReconnect为always时的乐观更新优化

问题核心

离线触发乐观更新后,mutation因网络失败进入重试队列;恢复在线时,refetchOnReconnect: 'always'会优先触发GET /preferences拉取旧数据,覆盖本地乐观更新的结果,直到mutation重试成功后才会再次拉取新数据,导致UI出现「新→旧→新」的跳变。

解决方案

方案1:通过enabled控制查询触发时机

在查询preferences时,监听是否有对应的mutation在执行,当mutation处于pending状态(包括离线排队的情况)时,禁用查询的自动触发:

import { useQuery, useIsMutating } from '@tanstack/react-query'

const PreferencesComponent = () => {
  // 监听针对preferences的mutation状态
  const isPreferencesMutating = useIsMutating({ queryKey: ['preferences'] })
  
  const { data } = useQuery({
    queryKey: ['preferences'],
    queryFn: fetchPreferences,
    // 当mutation在进行时,禁用查询自动执行
    enabled: !isPreferencesMutating,
    networkMode: 'offlineFirst',
    refetchOnReconnect: 'always'
  })

  // ...其他业务逻辑
}

这样恢复在线时,只有mutation重试完成后,查询才会执行,避免旧数据覆盖乐观更新结果。

方案2:临时修改查询的staleTime

在mutation的onMutate中临时将查询的staleTime设为无限,让react-query认为本地数据是新鲜的,不会在恢复在线时立即refetch;待mutation完成后再恢复默认值:

const queryClient = useQueryClient()

return useMutation({
  mutationFn: updatePreferences,
  
  onMutate: async (newPreferences) => {
    await queryClient.cancelQueries({ queryKey: ['preferences'] })
    const previousPreferences = queryClient.getQueryData(['preferences'])
    // 临时设置无限staleTime
    const originalStaleTime = queryClient.getQueryDefaults(['preferences'])?.staleTime
    queryClient.setQueryDefaults(['preferences'], { staleTime: Infinity })
    
    queryClient.setQueryData(['preferences'], (old) => [...old, newPreferences])
    return { previousPreferences, originalStaleTime }
  },
  
  onError: (err, newPreferences, context) => {
    queryClient.setQueryData(['preferences'], context.previousPreferences)
    // 恢复原staleTime
    queryClient.setQueryDefaults(['preferences'], { staleTime: context.originalStaleTime })
  },
  
  onSettled: (_data, _err, _variables, context) => {
    // 恢复原staleTime
    queryClient.setQueryDefaults(['preferences'], { staleTime: context.originalStaleTime })
    queryClient.invalidateQueries({ queryKey: ['preferences'] })
  },
})

方案3:在重试前取消查询

利用mutation的retryDelay回调,每次重试前取消正在进行的preferences查询,避免旧数据覆盖:

const queryClient = useQueryClient()

// 自定义网络错误判断逻辑
const isNetworkError = (err) => {
  return err.message.includes('NetworkError') || err.name === 'TypeError' /* 根据实际错误类型调整 */
}

return useMutation({
  mutationFn: updatePreferences,
  
  onMutate: async (newPreferences) => {
    await queryClient.cancelQueries({ queryKey: ['preferences'] })
    const previousPreferences = queryClient.getQueryData(['preferences'])
    queryClient.setQueryData(['preferences'], (old) => [...old, newPreferences])
    
    return { previousPreferences }
  },
  
  onError: (err, newPreferences, context) => {
    // 仅非网络错误时回滚数据
    if (!isNetworkError(err)) {
      queryClient.setQueryData(['preferences'], context.previousPreferences)
    }
  },
  
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ['preferences'] })
  },
  
  // 每次重试前取消查询
  retryDelay: () => {
    queryClient.cancelQueries({ queryKey: ['preferences'] })
    return 100 // 自定义重试延迟
  }
})

方案4:调整全局refetchOnReconnect配置

如果业务允许,将查询的refetchOnReconnect改为'ifStale',仅当本地数据过期时才会在恢复在线时refetch,结合乐观更新后的新鲜数据状态,避免不必要的refetch:

const clientConfig = {
  defaultOptions: {
    queries: {
      networkMode: 'offlineFirst',
      retry: 1,
      refetchOnReconnect: 'ifStale' // 替换为ifStale
    },
    mutations: {
      networkMode: 'offlineFirst',
      retry: 1
    }
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 10:12:54