news 2026/9/9 21:39:27

TanStack Query(React Query)Network Mode 全解析:online / always / offlineFirst 三种联网模式的实现原理与实战配置

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TanStack Query(React Query)Network Mode 全解析:online / always / offlineFirst 三种联网模式的实现原理与实战配置

TanStack Query(React Query)Network Mode 全解析:online / always / offlineFirst 三种联网模式的实现原理与实战配置

【免费下载链接】query🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query

TanStack Query(本仓库中对应@tanstack/react-queryquery-core等包)内置了三档networkMode,用于精确控制断网时 Query 与 Mutation 的行为:是直接不执行、照常执行,还是先执行一次再暂停重试。本文以 Network Mode 指南 为骨架,逐一拆解'online''always''offlineFirst'三种模式的语义、默认值与适用场景,并结合query-core源码(retryer.tsonlineManager.tsquery.ts)讲清fetching / paused / idlefetchStatus的底层切换逻辑,帮助你为离线 PWA、本地存储查询和普通在线应用选择正确的联网策略。

三种网络模式速览

TanStack Query 通过NetworkMode这一联合类型来区分在线 / 离线行为,其定义位于 types.ts:

export type NetworkMode = 'online' | 'always' | 'offlineFirst'
模式无网络时是否首次执行queryFn失败后重试refetchOnReconnect默认值典型场景
'online'(默认)不执行,进入paused暂停,待恢复连接后继续true绝大多数在线数据请求
'always'总是执行不暂停,失败直接进入errorfalse读本地AsyncStorage/ 纯内存计算,无需真实网络
'offlineFirst'执行一次首次失败后暂停重试trueService Worker 离线缓存、Cache-ControlHTTP 缓存

设置层级为“单条 Query / Mutation 局部配置,或通过 Query / Mutation 的全局默认值统一配置”,默认值固定为'online'。该类型同时被 QueryOptions 与 MutationOptions 引用,因此 Query 与 Mutation 两套体系共享同一套联网语义。

如何配置 networkMode

单条 Query 上设置

最常见的做法是直接在useQuery的选项中声明:

import { useQuery } from '@tanstack/react-query' // 本地优先:即使断网也尝试读取本地缓存 useQuery({ queryKey: ['local-counter'], queryFn: async () => { const stored = await readFromAsyncStorage('counter') if (stored !== null) { return stored } throw new Error('cache miss, need network') }, networkMode: 'offlineFirst', }) // 强制忽略在线状态 useQuery({ queryKey: ['computed-value'], queryFn: () => Promise.resolve(5), // 根本不需要网络 networkMode: 'always', })

单条 Mutation 上设置

Mutation 与 Query 使用同一选项名:

import { useMutation } from '@tanstack/react-query' const sendLog = useMutation({ mutationFn: (payload) => api.send(payload), networkMode: 'always', // 例如发送到本地队列,不依赖真实网络 })

全局默认值

既可以通过QueryClientdefaultOptions全局兜底,也可以配合queryClient.setQueryDefaults/setMutationDefaultsqueryKey命中局部默认值:

import { QueryClient } from '@tanstack/react-query' const queryClient = new QueryClient({ defaultOptions: { queries: { networkMode: 'online' }, // 全部查询默认值 mutations: { networkMode: 'online' }, // 全部变更默认值 }, }) queryClient.setQueryDefaults(['user', 'preferences'], { networkMode: 'offlineFirst' })

关于默认值还有一个容易被忽略的依赖规则:在queryClient.defaultQueryOptions内部,当显式传入persister未显式声明networkMode时,会自动将其推导为'offlineFirst'(见 queryClient.ts),这为持久化(persist)场景提供了合理的默认联网行为,具体在“与 Persister 的联动”一节展开。

Network Mode: online(默认,最常用)

'online'模式下,没有网络连接时 Query 与 Mutation 不会触发。这是默认模式,也是绝大多数数据获取库的合理选择。

如果某个查询在断网情况下被请求获取数据,它会在statepending/error/success)中保持不变——也就是说查询并不会凭空失败,而是停留在当前状态等待网络恢复。与此同时,查询额外暴露了一维fetchStatus用来描述“fetch 管道本身的运行情况”,可能取值如下:

  • fetchingqueryFn真正在执行,请求正在飞行中(in-flight);
  • paused:查询未在执行,因断网而暂停,直到重新获得连接;
  • idle:查询既没有在抓取,也没有被暂停。

为便于使用,isFetchingisPaused两个布尔量由fetchStatus派生而来,见 queryObserver.ts:

const isFetching = newState.fetchStatus === 'fetching' const isPaused = newState.fetchStatus === 'paused'

注意:不要只看pending来渲染加载态

指南中特别强调了一处反直觉陷阱:首次挂载且断网时,查询可能处于state: 'pending'fetchStatus: 'paused'。此时若 UI 只判断isPending就去渲染 loading spinner,页面会一直转圈而毫无意义,因此判断是否展示加载指示器时应结合fetchStatus

const { isPending, fetchStatus } = useQuery({ queryKey: ['data'], queryFn: fetchData }) const showSpinner = isPending && fetchStatus === 'fetching' // 而不是只看 isPending

抓取过程中掉线:暂停的是“重试”,不是查询本身

如果一个查询在在线时启动成功,但请求尚未返回时设备突然断网,TanStack Query 会暂停重试(retry)机制,待网络恢复后再继续执行(这是对中断 fetch 的继续continue,而非一次全新的refetch)。这在retryer中被实现为“可继续时继续,否则挂起等待”,其判定条件同时要求窗口聚焦且处于在线状态:

// packages/query-core/src/retryer.ts#L110-L113 const canContinue = () => focusManager.isFocused() && (config.networkMode === 'always' || onlineManager.isOnline()) && config.canRun()

这条暂停/继续链路的关键点在于:

  • refetchOnReconnect相互独立refetchOnReconnect(此模式下默认true)触发的是“重连后的重新抓取”,而这里发生的是一次“继续”动作。在 query.ts 的onOnline中可以看到两者同时被处理:先按shouldFetchOnReconnect触发refetch,再调用this.#retryer?.continue()唤醒被暂停的请求。
  • 如果查询在这期间被取消(如组件卸载触发的静默取消,详见 Query Cancellation),那么它不会再继续——cancel会使底层 retryer 的 Promise 以CancelledError收尾。

Network Mode: always(始终执行,忽略在线状态)

'always'模式下,TanStack Query始终发起抓取并完全忽略在线/离线状态。指南给出两个典型例子:queryFn内部只读取AsyncStorage,或者直接返回Promise.resolve(5)——即“查询本身根本不需要真实网络”的场景。

该模式的边界行为可从源码逐条得到印证:

  • 查询永远不会因为断网而进入pausedcanFetch'always'直接返回true
// packages/query-core/src/retryer.ts#L53-L57 export function canFetch(networkMode: NetworkMode | undefined): boolean { return (networkMode ?? 'online') === 'online' ? onlineManager.isOnline() : true }
  • 重试也不会暂停:请求若失败,将按重试策略(默认最多 3 次,见retryer.run中的config.retry ?? (isServerEnvironment() ? 0 : 3))走完,最终使查询进入error状态,不会卡在半路。
  • refetchOnReconnect默认为false:重连网络已不再是“陈旧数据应当刷新”的有效信号。这一默认值并非写死在选项里,而是在 queryClient.ts 的 defaultQueryOptions 中按模式推导出来的:
if (defaultedOptions.refetchOnReconnect === undefined) { defaultedOptions.refetchOnReconnect = defaultedOptions.networkMode !== 'always' }

如果确实希望在'always'模式下依然响应重连,可以手动将refetchOnReconnect重新打开。

Network Mode: offlineFirst(折中方案)

'offlineFirst'是前两种模式之间的折中:TanStack Query 会让queryFn先执行一次,但之后的重试会被暂停。这一点对两类缓存场景非常友好:

  • 带 Service Worker 拦截请求的离线优先 PWA:首屏请求可能命中 Service Worker 缓存而成功;
  • 依赖Cache-Control头的 HTTP 缓存:首次请求可能由浏览器 HTTP 缓存直接命中。

在这种情形下,如果第一次抓取来自离线存储/缓存并成功,查询正常完成;如果发生缓存未命中,真实网络请求发出后失败,此时该模式的表现退化为'online'——暂停后续重试,等待网络恢复。

这一“先试一次、失败再暂停”的策略同样可从源码验证:retryer.start()中,只要canFetch为真('offlineFirst''always'一样返回true),首次就会真正run()一次,只有进入失败重试路径的sleep之后才轮到canContinue/pause判定:

// packages/query-core/src/retryer.ts#L227-L235 start: () => { // Start loop if (canStart()) { run() } else { pause().then(run) } return promise },

底层实现:retryer、OnlineManager 与恢复链路

OnlineManager:在线状态的唯一事实来源

onlineManager是全局单例(onlineManager.ts),其内部默认#online = true,并通过订阅window上的online/offline事件来更新状态(onlineManager.ts)。暴露的核心接口为:

  • onlineManager.isOnline(): boolean——读取当前在线状态;
  • onlineManager.setOnline(online: boolean)——手动设置(Devtools 的 Mock 按钮正是走这条路);
  • onlineManager.subscribe(listener)——订阅在线状态变化。

关于默认在线状态的说明可参考 onlineManager 参考文档:项目刻意不再依赖navigator.onLine作为初始值(Chromium 系浏览器中它有大量误判为离线的历史问题),而是默认online: true,仅靠事件驱动更新,以降低“假离线”误判概率;代价是经 Service Worker 加载的离线应用可能短暂出现“假在线”,因为它们确实可以在没有公网连接的情况下工作。

pause / continue 与状态机

'online'模式下,若断网启动查询,canFetch返回falseretryer.start()进入pause()pause()返回一个 Promise,其continueFn只有在isResolved()canContinue()成立时才会放行,从而真正执行run()fetchStatus也随之在pausedfetching之间切换(对应 query.ts 中的状态 dispatch)。

恢复连接的完整链路是:windowonline事件 →onlineManager广播 → Query 的onOnline()(见 query.ts)→ 触发refetchOnReconnect判断 +retryer.continue()→ 被暂停的抓取/重试继续执行。

Mutation 同样遵循 networkMode

Mutation 在构建其内部 retryer 时会把当前networkMode传入(见 mutation.ts),并且用!retryer.canStart()推导自身的 paused 状态(mutation.ts),因此 Mutation 也会在断网时暂停,并在恢复后继续,相关行为的完整指南见 Mutations。

与 Persister 的联动:自动降级为 offlineFirst

使用createPersister/persistQueryClient这类持久化方案时,写缓存通常依赖本地存储而不依赖网络。为了让持久化查询在“首次尝试本地读取、命中即成功、未命中才等网络”的语义下运行,defaultQueryOptions加入了自动推导规则(queryClient.ts):

if (!defaultedOptions.networkMode && defaultedOptions.persister) { defaultedOptions.networkMode = 'offlineFirst' }

即:只要指定了persister且没有显式写死networkMode,就会被自动设置为'offlineFirst'。这意味着离线优先的持久化读取几乎开箱即用;若希望强制走'online''always',则需显式声明以覆盖该推导值。

Devtools:观察 paused 与模拟离线

TanStack Query Devtools 会以paused状态展示“本想抓取、但因断网未能执行”的查询,便于在开发阶段直接观察联网策略是否生效。

Devtools 面板中还提供了一个Mock offline behavior(模拟离线行为)的切换按钮。请特别注意:这个按钮并不会真的切断你的网络连接(真正断网可在浏览器 Devtools 的 Network 面板完成),它的实现只是把OnlineManager置为离线状态——源码中对应一次setOnline取反(见 query-devtools 的 Devtools.tsx):

onlineManager().setOnline(!onlineManager().isOnline())

正因如此,通过该按钮可以零成本地验证你的 Query / Mutation 在三种networkMode下分别会表现出fetchingpaused还是直接失败——与fetchStatus的观察相互印证。

Signature(配置签名速查)

networkMode的完整类型签名如下:

  • networkMode: 'online' | 'always' | 'offlineFirst'
    • 可选
    • 默认值为'online'

选型建议小结

  • 常规在线请求:保持默认'online',并记得结合fetchStatus === 'paused'正确渲染加载/离线提示;
  • 查询纯依赖本地数据(AsyncStorage、内存计算):'always'
  • 有 Service Worker 或Cache-Control缓存兜底、愿意“先试一次、失败再等网络”的离线优先 PWA:'offlineFirst'
  • 接入了persister却未显式声明联网模式:框架已自动使用'offlineFirst',无需重复配置。

配合 Queries(fetchStatusisPaused定义)、Mutations 以及 query-core 源码 阅读本文,即可完整掌握 TanStack Query 在离线场景下的行为边界。

【免费下载链接】query🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 21:36:51

2026年Claude Code插件精选:9款效率工具与避坑指南

如果你刚把 Claude Code 环境跑通,大概率会和我一样:看到网上推荐插件就忍不住装,结果终端助手没更快,账单倒是先涨了。Claude Code 本身保留了很高的扩展空间,但这不意味着装得越多越好,真正能提升日常生产…

作者头像 李华
网站建设 2026/9/9 21:35:22

Seelen UI:10分钟定制你的Windows桌面环境完整指南

Seelen UI:10分钟定制你的Windows桌面环境完整指南 【免费下载链接】Seelen-UI The Fully Customizable Desktop Environment for Windows 10/11. 项目地址: https://gitcode.com/GitHub_Trending/se/Seelen-UI 还在手动拖拽窗口、一个挨一个排位置吗&#x…

作者头像 李华
网站建设 2026/9/9 21:34:42

诺特定理:从对称性到守恒律的物理美学与工程实践

如果你问我,物理定律里最优雅的一条是什么,我会毫不犹豫地提名诺特定理。很多人第一次听到这个名字,是在分析力学或者理论力学的课上,老师用半节课讲完,然后用一串令人眼花缭乱的推导告诉你:对称性对应守恒…

作者头像 李华
网站建设 2026/9/9 21:34:11

基于SpringBoot的养老院管理信息系统设计与实现

做毕设选管理系统类题目,最怕的就是“看起来简单,写起来没料”。养老院管理信息系统这个题目,是我觉得在SpringBoot方向里性价比很高的一个选题:业务上覆盖了老人档案、护理工单、床位分配、费用结算、家属查询这些真实场景&#…

作者头像 李华
网站建设 2026/9/9 21:34:07

2026缓存数据库选型指南:Redis、Tair与Memcached横评对比

前言缓存数据库选型这话题,放到 2026 年再看,反而比前几年更有意思了。以前大家基本是“Redis 一统天下、Memcached 老骥伏枥、Tair 阿里内部自用”这个格局,但这两年云厂商把 Tair 推到前台,Redis 开源协议又经历了好几轮变更&am…

作者头像 李华