@tanstack/query-core 5.102 版本解读:query/infiniteQuery 新命令式 API、hydration 性能优化与内存释放
【免费下载链接】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-core是 TanStack Query 框架无关的核心层,react-query、vue-query、solid-query、svelte-query 等框架适配包均建立在它之上。本文以 packages/query-core/CHANGELOG.md 为主线,逐条解读 5.90 至 5.102.8 版本的关键变更,并结合 queryClient.ts、hydration.ts、environmentManager.ts 等源码,说明这些变更对实际开发(SSR 水合、Suspense、无限查询、内存占用、类型推断)的影响,帮助你在升级时准确评估风险与收益。
一、版本概览与当前状态
根据 package.json,当前仓库中@tanstack/query-core的版本为5.102.8,包描述为 "The framework agnostic core that powers TanStack Query",采用type: "module",同时提供import/require双通道产物与 modern/legacy 两套类型声明。
CHANGELOG 覆盖的版本区间与核心主题如下:
| 版本区间 | 主题关键词 |
|---|---|
| 5.102.x | 新命令式 API(query/infiniteQuery)、hydration 性能、内存释放、类型改进 |
| 5.100.x – 5.101.x | 泛型推断修复(NoInfer)、retryOnMount回调、SSR 水合行为 |
| 5.90.x – 5.99.x | environmentManager、streamedQuery、timeoutManager、observer 调度修复 |
其中5.102.0是信息量最大的一个 Minor 版本,值得单独展开。
二、5.102.0:新的命令式 APIquery与infiniteQuery
5.102.0 引入了一组新的命令式方法,并弃用了旧的fetchQuery、prefetchQuery、fetchInfiniteQuery、prefetchInfiniteQuery、ensureQueryData等 API。
2.1 新增方法与用法
// 主动拉取并返回 Promise,等价于旧的 fetchQuery await queryClient.query({ queryKey: ['posts'], queryFn: fetchPosts, }) // 无限查询版本,内部自动带上 _type: 'infinite' await queryClient.infiniteQuery({ queryKey: ['posts', 'infinite'], queryFn: fetchPage, initialPageParam: 0, }) // 设置 staleTime 为 'static',等价于旧的 ensureQueryData 语义 await queryClient.query({ queryKey: ['posts'], queryFn: fetchPosts, staleTime: 'static' })从 queryClient.ts 源码 可以看到infiniteQuery的实现非常简洁——它先把options._type = 'infinite'写入选项,再委托给query():
infiniteQuery(options) { options._type = 'infinite' return this.query(options as any) }_type字段正是 hydration.ts 中DehydratedQuery.queryType(值可为'infinite')的来源,也是query()在底层构建InfiniteQueryObserver行为的类型标记。
2.2 旧方法上的弃用标记
源码中对旧方法全部添加了@deprecatedJSDoc 标记,例如:
/** @deprecated Use queryClient.query(options) instead. This method will be removed in the next major version. */ fetchQuery(...) /** @deprecated Use queryClient.infiniteQuery({ ...options, staleTime: 'static' }) instead. ... */ ensureQueryData(...)完整列表见 queryClient.ts。这意味着升级到 5.102 后,使用旧方法的代码会收到类型层面的弃用警告;下个大版本将直接移除,建议在升级本版本时同步迁移。
2.3 相关类型改进
同一版本中还修复了两个与类型相关的问题:
queryOptions/infiniteQueryOptions返回类型:修复导出类型在声明文件中泄露内部 data tag 符号的问题(PR #11224),使推断出的 options 可以安全地发布到.d.ts中;- mutation variables 可选性:当
undefined extends TVariables时,mutation 的variables参数变为可选(PR #8737),避免了对可能为undefined的变量强行必填。
三、hydration 相关变更:性能、导出与行为修复
3.1 导出dehydrateQuery(5.102.0)
5.102.0 将dehydrateQuery从内部函数提升为公开导出,index.ts 与 hydration.ts 中均有对应实现。它的签名如下:
export function dehydrateQuery( query: Query, serializeData?: TransformerFn, shouldRedactErrors?: (error: unknown) => boolean, ): DehydratedQuery它返回包含dehydratedAt、state(可被serializeData序列化)、queryKey、queryHash的对象;当查询状态为pending时还会附带一个promise字段(由dehydratePromise生成,见 hydration.ts)。在非生产环境下,若被水合的 pending promise 最终 reject,会打印包含queryHash的红色错误日志,生产环境则统一替换为'redacted'错误,避免泄漏服务端错误细节。
3.2 hydration 性能优化(5.102.0)
- 跳过无操作的数据变换与默认错误脱敏回调(PR #11253):在
dehydrate过程中,若未配置serializeData或shouldRedactErrors,则不再调用默认的 no-op 变换函数,减少不必要的函数调用开销; - pending 查询的 de/rehydration 不再产生未处理的 Promise 拒绝(5.90.3,PR #9752):
dehydratePromise内部对返回的 promise 追加.catch(noop)(见 hydration.ts),既保证查询缓存能拿到正确结果,又避免在预取环境产生 unhandled rejection 警告。
3.3 水合行为修复
- 5.100.1:修复"已解析的 promise 被水合时查询短暂显示 pending/fetching"的问题(PR #10444)。这正是 hydrate 实现 中
tryResolveSync(promise)的作用——同步解析成功的数据直接进入 state,不再走 retryer 路径,从而避免闪烁; - 5.102.1:
hydrate的入参类型从DehydratedState放宽为Partial<DehydratedState>(PR #11260),允许省略 mutation 或 query 集合(例如只有 queries 的 payload),hydrate 签名 已同步更新,且内部用?.forEach做可选遍历。
3.4 无限查询在 SSR 水合中的行为保持(5.100.2)
5.100.2 修复了无限查询在 SSR 水合期间的行为(PR #10074):dehydrateQuery中queryType字段('infinite')被保留,hydrate时通过_type: queryType传递(见 hydration.ts),确保水合后的无限查询仍以正确的 infinite 语义运行。
四、内存与资源释放:retryer 与订阅生命周期
5.102.0 集中修复了一批长生命周期对象持有短生命周期引用导致的内存滞留问题,这是本次版本最值得关注的一类修复:
- 查询 retryer 释放(PR #11163):fetch 结束(settle)后立即释放查询的 retryer,已 settled 的 promise 不再持有 fetch 原始结果,与结构共享的
state.data并存的内存占用被消除;查询被 reset 或 remove 后同样生效; - mutation retryer 释放(PR #11218):mutation 执行结束后释放其 retryer,mutation 的 result、variables 与 context 不再因 mutation cache 的保留而长期驻留内存;
- observer 列表原地移除(PR #11214):
unsubscribe时不再复制 observer 列表,改为原地删除,降低每次退订的 churn 开销; - 定时器泄漏修复(5.95.1 / 5.95.2,PR #10323 / #10325):确保 Node.js 的
Timeout不会泄漏——注意 timeoutManager.ts 是专门管理超时调度的模块; - timer ID 边界情况(5.97.0,PR #10401):改用显式
undefined判断 timer ID,使自定义TimeoutProvider返回0作为合法 timer ID 时也能被正确清除。
五、Suspense 与 observer 通知行为
- 程序化数据更新也能解除 Suspense(PR #11036):此前
fetchOptimistic只返回 fetch promise,即使缓存中已有setQueryData或streamedQuery写入的数据,Suspense 边界也要等queryFn完成才能解除。修复后通过Promise.race配合 cache 订阅,一旦数据可用立即解除 Suspense; - pendingThenable 的滞后回调不再覆盖状态(PR #11128):持有
resolve/reject引用并在 settled 之后调用,曾导致 thenable 的status/reason与实际 promise 不一致,现已被忽略; - suspense 模式下跳过 combine(5.100.3,PR #10576):查询即将挂起时不调用
combine,避免无谓计算; useSuspenseQueries重复 queryKey 防死循环(5.90.11,PR #9886):重复 key 不再引发无限渲染循环;- 同步退订通知(PR #11234):同一查询更新期间,若有 observer 同步退订,仍会通知到其余所有 observer;
resetQueries保留匹配集(PR #11211):修复query.reset()改变状态前已匹配的查询集合被丢失的问题;isPlaceholderData与 select 错误清理(PR #11161、PR #11011):切换到无数据的查询时清除过期的select错误,select在 placeholder 数据上抛错时重置isPlaceholderData,避免上一个查询的错误泄漏到新结果。
useQueries 性能专项(5.102.0)
- 无 combine 时跳过结果跟踪(PR #11225):通知
useQueries监听器时,若未提供combine函数,跳过不必要的 result tracking; - 避免重复同步同一 tracked 属性(PR #11215):不再对所有 observer 反复同步同一属性;
- falsy combine 结果记忆化(PR #11065):当
combine函数与查询结果均未变化时,记忆化 falsy 结果(如null),避免引用不稳定引发重渲染; - 动态变化时更新 stable combine 引用(5.90.19,PR #9954):查询动态变化时,稳定的
combine引用也能正确更新; - 查询数量变化的竞态(5.90.16,PR #9973):修复
useQueries在查询长度变化时的竞态条件。
六、类型系统改进(5.100.x 系列)
- 弃用自定义
NoInfer,改用 TS 内置(5.100.13,PR #10593):要求TypeScript ≥ 5.4。这是为了修复NoInfer<X[K]>在泛型上下文中不满足X[K]可赋值性的问题(issue #9937)。注意这是升级时的一个硬性前提条件,请先确认项目 TS 版本; - persister 泛型推断调整(5.100.2 / 5.100.10,PR #10510 / #10601):允许
persister参与TQueryFnData推断(修复声明了参数类型的queryFn与 typed persister 的虚假重载不匹配,issue #7842),同时保留persister槽位上的NoInfer<TQueryKey>,防止TQueryKey被拓宽到增强后的约束,避免DataTag品牌化返回值在逆变位置不可赋值; - QueryFilters 联合类型(5.90.10 / 5.90.9 / 5.90.8):允许不同长度的
QueryFilters联合、支持部分 query key 匹配、不丢失 readonly 修饰; - MutationKey 类型去重(5.90.4,PR #9754):移除
MutationKey中重复的Array条件分支。
七、核心配置导出与新管理器
7.1 导出QueryCacheConfig与MutationCacheConfig(5.102.2)
这两个配置类型此前仅存在于 queryCache.ts 与 mutationCache.ts 中,5.102.2 起通过 index.ts 对外导出,允许自定义缓存时获得完整类型提示。两者均接受onError、onSuccess、onSettled等回调(见对应测试 queryCache.test.tsx 与 mutationCache.test.tsx)。
7.2environmentManager(5.91.0,PR #10199)
新增环境检测管理器,源码见 environmentManager.ts:
export const environmentManager = { isServer, // () => boolean setIsServer(isServerValue: IsServerValue), // 全局覆盖服务端检测 }它允许在测试或特殊运行时(如边缘函数)中覆写isServer的判断结果。相关行为由 environmentManager.test.tsx 覆盖,并在 focusManager.ts、onlineManager.ts 等模块中配合使用。
八、streamedQuery 与 AbortSignal 相关修复
- 错误状态下 reset refetch 保持错误(5.91.2,PR #10287):定义了
initialData时,reset refetch 不再丢失 error 状态; - reducer 只调用一次(5.90.14,PR #9970):修复
streamedQueryreducer 被重复调用的问题; - signal 感知(5.90.13,PR #9963):
context.signal对streamedQuery生效; - 空流不返回 undefined(5.90.10,PR #9876):流没有产出值时不再返回
undefined; dataUpdatedAt缺失(5.101.1,PR #10610):修复在水合前已 resolve 的流式查询缺少dataUpdatedAt的问题;- 无限查询的 AbortSignal reason 传播(5.100.7):abort 时自定义的
reason能正确传递给无限查询。
九、其余值得关注的 Patch 修复
- disabled 查询 observer 不再调度 stale timeout(5.102.4,PR #11293):避免为已禁用的 observer 安排无意义的重取定时器;
MutationObserver重新附加(PR #11172):React 在 mutation 中途 tear down 并重建订阅时,observer 会重新挂到当前 mutation 上,useMutation结果不再卡在pending;onMutate同步执行(5.90.20,PR #10066):未配置mutationCache.config.onMutate时,onMutate回调改为同步运行,减少竞态窗口;- 错误状态下现有数据视为过期(5.90.15,PR #9927):查询进入错误状态时,已有数据一律视为 stale,促使尽快重取;
replaceEqualDeep最大深度(5.90.17,PR #10032):修复深度过大时的递归问题;partialMatchKey性能(5.101.3,PR #11084):优化部分 key 匹配的底层实现;- 包体精简(5.102.5,PR #11302):移除未使用的 symbol description、简化内部辅助函数,降低 query-core 的 bundle 体积;
isFetchedAfterMount修正(5.90.6,PR #9743):应用initialData的场景下该标记行为更准确;- 水合时的
.then/.catch挂载时机(5.90.7,PR #9847):只有 promise 确实被 dehydrate 时才附加.then/.catch; - 最后 observer 退订时取消暂停的初始 fetch(5.91.1,PR #10291):避免无人消费的请求继续占用资源;
- 移除实验性 render-time prefetching(PR #11221):删除
experimental_prefetchInRender相关能力与查询结果上的promise属性,属于破坏性清理,若你的代码依赖实验 API 需注意(5.90.18 曾对齐其 rejection 行为,5.102.0 正式移除)。
十、升级建议与验证方式
- TypeScript 版本:5.100.13 起依赖 TS ≥ 5.4 的内置
NoInfer,升级前先确认; - API 迁移:将
fetchQuery/prefetchQuery/fetchInfiniteQuery/prefetchInfiniteQuery/ensureQueryData迁移到queryClient.query/queryClient.infiniteQuery,ensureQueryData语义对应staleTime: 'static';留意实验性 prefetch API 已被移除; - 水合 payload:
hydrate已接受 partial state,若你手写水合数据可省略空集合;dehydrateQuery现已公开导出,可直接复用; - 内存敏感场景:长驻 cache 的查询/突变不再持有 settled retryer,可关注监控内存下降;自定义
TimeoutProvider若返回0作为 timer ID,升级后可被正确清除; - 验证:仓库提供多版本 TS 类型测试(见 package.json 中
test:types:*,覆盖 TS 5.6 至 7.0),核心逻辑测试可运行pnpm --filter @tanstack/query-core test:lib(vitest)。示例用法可参考 react 示例 与 vue 示例 中各项目的queryClient配置。
结语
5.90 至 5.102.8 的@tanstack/query-core演进,主线非常清晰:统一并现代化命令式 API、系统性消除内存滞留、为 SSR/Suspense 场景打磨水合行为、并在不牺牲类型安全的前提下提升运行时性能。升级时优先处理 API 迁移与 TS 版本两个前置条件,即可平滑享受这些底层改进带来的收益。
【免费下载链接】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),仅供参考