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

