TanStack Preact Query 的重要默认配置(Important Defaults)深度解析
【免费下载链接】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 Preact Query 开箱即用,但默认值往往是新用户"踩坑"与调试困难的源头:缓存数据一律视为过期、失败静默重试 3 次、闲置查询 5 分钟后被回收……本指南以 docs/framework/preact/guides/important-defaults.md 为骨架,逐条拆解 Preact Query 各项默认行为,并结合仓库内@tanstack/query-core源码给出实现依据与自定义方案。读完你将能熟练通过staleTime、gcTime、retry等选项掌控数据的过期、回收与重试节奏,让请求行为完全符合预期。
默认值设计哲学:aggressive but sane
TanStack Query 系列(Preact Query、React Query、Vue Query、Solid Query 等)共享同一套查询内核@tanstack/query-core。在 Preact 应用中,packages/preact-query/src/index.ts 直接export * from '@tanstack/query-core',因此本文讨论的默认值行为在所有框架适配层上完全一致。
这些默认值被官方定位为aggressive but sane(激进但理智):它们追求"开箱即用即可获得合理体验",但若使用者对其不了解,往往会因出乎意料的自动行为而困惑。下文是每一个默认行为的完整盘点,以及对应的调节入口。
默认一:缓存数据一经获取即视为 stale
通过
useQuery或useInfiniteQuery创建的查询实例,默认会把缓存中已有的数据视为过期(stale)。
这背后对应staleTime的默认值为0。其语义是:只要新查询实例挂载、窗口重新聚焦或网络恢复,库就会去后台重新请求以刷新数据。想要改变该行为,可以全局或按查询设置staleTime。
staleTime 的三种典型取值
staleTime表示"数据在多少毫秒内保持新鲜"。设置了staleTime的查询,在该时间到期前会被视为fresh(新鲜):
- 设置
staleTime: 2 * 60 * 1000:2 分钟内数据始终从缓存直接读取,不会触发任何形式的 refetch,除非查询被手动失效; - 设置
staleTime: Infinity:永不因过期而触发 refetch,直到查询被手动失效; - 设置
staleTime: 'static':永不触发 refetch,即使查询被手动失效也无效。
'static' 与 Infinity 的关键差异
'static'与Infinity都能阻止基于过期的自动 refetch,但'static'更严格:
queryClient.invalidateQueries()可以失效staleTime: Infinity的查询,但对staleTime: 'static'的查询没有任何作用;- 设置为
"always"的refetchOnMount、refetchOnWindowFocus、refetchOnReconnect也一律被'static'阻止。
该差异在源码中体现得非常直接。packages/query-core/src/query.ts 中isStaleByTime()的判定顺序是:无数据视为 stale →'static'直接返回false(永不 stale)→ 已被 invalidated 视为 stale → 依据dataUpdatedAt与staleTime计算剩余新鲜时间。正因为'static'的短路判断位于 invalidated 判断之前,失效操作对它无效。此外 query.ts 的isStatic()会检查当前观察者中是否存在staleTime: 'static'的选项。
选型建议:
'static'适合应用运行期间绝不可能变化的数据——启动时拉取的 feature flags、登录后加载的用户权限、静态参考表等;Infinity适合仍然希望保留手动失效能力的数据。
默认二:stale 查询在三个时机自动后台刷新
stale 查询会在以下时机被自动后台 refetch:
- 有新的查询实例挂载(
refetchOnMount生效); - 窗口重新聚焦(
refetchOnWindowFocus生效); - 网络重新连接(
refetchOnReconnect生效)。
设置更长的staleTime是避免过度 refetch 的推荐做法;此外也可以直接定制这三个触发点,例如将refetchOnWindowFocus设为false或'always',精细控制自动刷新的发生时机。其中refetchOnReconnect还有一个底层联动:在 packages/query-core/src/queryClient.ts 中,当它未显式定义时会被默认推导为networkMode !== 'always',即"网络模式下才在重连时刷新"。
三个触发点在query层同样有对应方法(如 query.ts 的onFocus()、onOnline()),分别由 focus 与在线状态管理器驱动。
默认三:refetchInterval 轮询与 staleTime 相互独立
查询可选用refetchInterval选项周期性地触发 refetch,它与staleTime是相互独立的两套机制——即使数据仍新鲜,只要设置了refetchInterval就会按固定间隔轮询。具体用法详见 Polling(轮询)指南。
默认四:闲置查询保留 5 分钟后回收
当某个查询不再有任何活跃实例(useQuery、useInfiniteQuery或底层观察者都被卸载)时,它会被标记为inactive(闲置),但仍然留在缓存中,以备后续再次使用。默认情况下,闲置查询会在5 分钟后被垃圾回收(garbage collected)。
对应源码是 packages/query-core/src/removable.ts 的updateGcTime():
// Default to 5 minutes (Infinity for server-side) if no gcTime is set this.gcTime = Math.max( this.gcTime || 0, newGcTime ?? (isServerEnvironment() ? Infinity : 5 * 60 * 1000), )值得注意的两点实现细节:
- 默认回收时长为
1000 * 60 * 5毫秒,即 5 分钟;服务端环境(SSR)默认为Infinity,避免回收尚未水合所需的数据; - 回收时长取
gcTime的最大值,且调度逻辑(removable.ts)会先清除旧定时器再重新调度,因此调大gcTime后旧数据不会立刻被误回收。
如要改变该时长,可将全局或单查询的gcTime改为其他毫秒值。例如对"重新进入页面频率较高、希望更久地保留状态"的查询设置gcTime: 30 * 60 * 1000。
默认五:失败查询静默重试 3 次,指数退避
查询失败时,默认静默重试 3 次,采用指数退避(exponential backoff)延迟,全部重试失败后才把错误暴露给 UI。
指数退避的实现在 packages/query-core/src/retryer.ts:
function defaultRetryDelay(failureCount: number) { return Math.min(1000 * 2 ** failureCount, 30000) }即第 1 次失败后等待约 2 秒(2^1),第 2 次约 4 秒,依次递增,并封顶 30 秒。若想改变,可覆盖retry与retryDelay两个选项:
retry:可设为false(不重试)、数字(重试次数)或(failureCount, error) => boolean回调;retryDelay:可设为固定毫秒数,或(failureCount, error) => number自定义延迟函数,实现线性退避、抖动(jitter)等策略。
补充:
queryClient层面还提供缓存型默认值。queryClient.setQueryDefaults()会为特定queryKey注册默认选项,例如"对所有posts键的查询默认重试 5 次";这些键级默认值会在defaultQueryOptions()中与全局默认、查询自身选项按优先级合并(见下节)。
默认六:结构共享(Structural Sharing)保持引用稳定
查询结果默认启用结构共享(structural sharing):库会检测新数据与旧数据是否"实质变化",若未变化则保持原数据引用不变。这有助于配合useMemo、useCallback做值稳定化,减少不必要的重渲染。初次接触该概念不必焦虑——绝大多数场景下无需关闭它,它几乎以零成本提升应用性能。
需要留意的边界与扩展:
- 结构共享仅对 JSON 兼容的值有效,其他值类型(如
Date、自定义类实例、Map/Set 等)永远被视为已变化; - 若因响应体巨大等原因出现性能问题,可通过
structuralSharing: false关闭该特性(类型定义见 packages/query-core/src/types.ts); - 若响应中存在非 JSON 兼容值,又希望正确判断是否变化,可以传入自定义函数作为
structuralSharing,由它基于新旧响应计算并保留所需引用。
如何在全局与单查询层面覆盖默认值
无论是staleTime、gcTime、retry还是structuralSharing,配置都有三层合并优先级,最终由 queryClient.ts 的defaultQueryOptions()统一执行:
const defaultedOptions = { ...this.#defaultOptions.queries, // ① QueryClient 全局默认 ...this.getQueryDefaults(options.queryKey), // ② 按 queryKey 的默认 ...options, // ③ 本次查询显式传入的选项 _defaulted: true, }全局配置示例
import { QueryClient } from '@tanstack/preact-query' export const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5 分钟内视为新鲜,不重复请求 gcTime: 30 * 60 * 1000, // 闲置 30 分钟后回收 retry: 2, // 失败重试 2 次 refetchOnWindowFocus: false, structuralSharing: true, }, }, })按查询配置示例
import { useQuery } from '@tanstack/preact-query' // 该查询独享 2 分钟新鲜期,且失败不重试 const { data } = useQuery({ queryKey: ['posts', id], queryFn: () => fetchPost(id), staleTime: 2 * 60 * 1000, retry: false, }) // 只读的静态配置,永不过期、无法被失效 const { data: flags } = useQuery({ queryKey: ['feature-flags'], queryFn: () => fetchFlags(), staleTime: 'static', })除useQuery外,useInfiniteQuery、useSuspenseQuery以及@tanstack/preact-query导出的queryOptions/infiniteQueryOptions帮助函数均支持同样的选项结构,可查看 packages/preact-query/src/index.ts 的完整导出清单。
默认值速查对照表
| 默认行为 | 默认值/时机 | 相关选项 | 如何调整 |
|---|---|---|---|
| 缓存数据一经获取即视为 stale | staleTime: 0 | staleTime | 设为毫秒数、Infinity或'static' |
| 挂载新实例时后台刷新 | stale 即触发 | refetchOnMount | false/'always' |
| 窗口聚焦时后台刷新 | stale 即触发 | refetchOnWindowFocus | false/'always' |
| 网络重连时后台刷新 | stale 即触发 | refetchOnReconnect | false/'always',默认受networkMode联动 |
| 定时轮询 | 不开启 | refetchInterval | 毫秒间隔(与staleTime独立) |
| 闲置查询回收 | inactive 后5 分钟 | gcTime | 其他毫秒值;SSR 下默认为Infinity |
| 失败静默重试 | 3 次,指数退避 | retry/retryDelay | 次数、布尔或自定义策略函数 |
| 结构共享 | 默认开启 | structuralSharing | false或自定义比较函数 |
默认值对调试心态的启示
文档在开头特别强调:这些默认值会在用户不知情时让学习与调试变难。实践中两个高频"惊吓点"分别是:
- "我没有写 refetch 代码,为什么页面一聚焦数据就变了?"——实为
refetchOnWindowFocus配合默认staleTime: 0在起作用; - "请求明明失败了,为什么 UI 迟迟不报错?"——实为默认重试 3 次 + 指数退避在按 2s/4s/8s 静默重试。
理解了本文的默认值体系后,这两类现象都可被准确预判。关于缓存失效与手动刷新之间的关系,可继续阅读 Query Invalidation 指南 与 Caching 指南 获取完整图景。该指南页面本身由官方文档生成机制从 React 版源文档 派生,社区深入讨论还可参见 Community Resources。
【免费下载链接】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),仅供参考