ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

TanStack Query 的 QueryCache 完全指南:查询缓存存储机制、实例查找与订阅实战

2026/9/11 5:38:53 拓冰建站 浏览量
TanStack Query 的 QueryCache 完全指南:查询缓存存储机制、实例查找与订阅实战 TanStack Query 的 QueryCache 完全指南查询缓存存储机制、实例查找与订阅实战【免费下载链接】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/queryQueryCache 是 TanStack Query 体系中所有查询数据的统一存储层负责保存每个查询的数据、元信息与完整状态。本篇指南以 docs/reference/QueryCache.md 为核心骨架结合仓库中 packages/query-core/src/queryCache.ts 的源码实现与 packages/query-core/src/tests/queryCache.test.tsx 的测试用例带你完整掌握 QueryCache 的构造选项、find/findAll/subscribe/clear四大方法以及它底层的存储、通知与查询生命周期机制。读完本文你将能够在实际项目中熟练地直接操纵查询缓存实例、精准筛选查询、监听缓存事件并实现自定义的缓存策略。QueryCache 是什么TanStack Query 的查询存储中枢QueryCache是 TanStack Query 的存储机制storage mechanism它保存了其内部所有查询Query的数据、元信息meta information和状态state。从源码看它的本质是一个以queryHash为键、以Query实例为值的 Map 容器// packages/query-core/src/queryCache.ts export class QueryCache extends SubscribableQueryCacheListener { #queries: QueryStore constructor(public config: QueryCacheConfig {}) { super() this.#queries new Mapstring, Query() } }需要注意的关键结论是通常情况下你不需要直接与 QueryCache 交互而是通过QueryClient来操作特定的缓存。在 React、Vue、Svelte、Solid、Angular、Lit、Preact 等各框架适配层中开发者日常使用的useQuery、queryClient.setQueryData、queryClient.invalidateQueries等方法最终都会落到这个核心类上。QueryCache 继承自SubscribableQueryCacheListenerpackages/query-core/src/subscribable.ts因此它天然具备订阅-通知能力这也是后面subscribe方法能够工作的基础。创建 QueryCache 实例与配置选项你可以在各框架包中导入QueryCache并直接实例化import { QueryCache } from tanstack/react-query const queryCache new QueryCache({ onError: (error) { console.log(error) }, onSuccess: (data) { console.log(data) }, onSettled: (data, error) { console.log(data, error) }, }) const query queryCache.find({ queryKey: [posts] })注意不同框架包的导出入口不同例如tanstack/vue-query、tanstack/svelte-query也会导出各自的 QueryCache。所有框架适配层共享同一份query-core实现。构造选项Options根据 queryCache.ts 中的QueryCacheConfig类型定义构造选项如下选项类型必填说明onError(error: unknown, query: Query) void否当某个查询发生错误时被调用onSuccess(data: unknown, query: Query) void否当某个查询成功时被调用onSettled(data: unknown \| undefined, error: unknown \| null, query: Query) void否当某个查询落定无论成功还是失败时被调用这三个回调是全局级的无论缓存中哪个查询发生状态变化只要命中对应的终态就会触发。它们的调用时机可以在 packages/query-core/src/query.ts 中找到确切的实现证据——在查询成功分支中依次调用cache.config.onSuccess与cache.config.onSettled在错误分支中依次调用cache.config.onError与cache.config.onSettled错误分支还会把错误rethrow以便上层继续处理。对应的测试用例位于 packages/query-core/src/tests/queryCache.test.tsx验证了查询出错时onError被调用一次、onSuccess不被调用、onSettled被调用一次且携带(undefined, error, query)查询成功时onSuccess被调用一次并携带(data, query)、onError不被调用、onSettled携带(data, null, query)。如何让 QueryClient 使用自定义 QueryCacheQueryCache 通常作为QueryClient的内部成员存在通过queryClient.getQueryCache()获取。你可以在创建 QueryClient 时注入自定义的 QueryCacheimport { QueryCache, QueryClient } from tanstack/react-query const queryCache new QueryCache({ onError: (error) console.error(全局查询错误:, error), }) const queryClient new QueryClient({ queryCache })这样做可以让全局错误处理等逻辑与查询客户端实例解耦。仓库测试中同样大量使用该模式例如在 queryCache.test.tsx 中通过new QueryClient({ queryCache: testCache })注入独立缓存来做隔离测试。queryCache.find按精确条件同步查找查询实例find是一个相对高级的同步方法用于从缓存中获取已存在的查询实例。返回的实例不仅包含查询的全部状态还包含查询的所有实例observers及其底层内部结构。如果查询不存在则返回undefined。const query queryCache.find({ queryKey })注意大多数应用通常不需要用到它但在某些少见场景下当需要获取某个查询的更多信息时会很有用。例如查看query.state.dataUpdatedAt时间戳来判断该查询的数据是否足够新鲜、能否直接作为初始值使用。参数说明filters: QueryFilters完整的筛选器定义见 Query Filters其中queryKey: QueryKey是必填项见 Query Keys。返回值Query缓存中的查询实例或undefined。源码级原理为什么 find 是精确匹配从源码看find的实现会强制把exact默认置为true再做一次线性扫描匹配// packages/query-core/src/queryCache.ts findTQueryFnData unknown, TError DefaultError, TData TQueryFnData( filters: WithRequiredQueryFilters, queryKey, ): QueryTQueryFnData, TError, TData | undefined { const defaultedFilters { exact: true, ...filters } return this.getAll().find((query) matchQuery(defaultedFilters, query), ) as QueryTQueryFnData, TError, TData | undefined }也就是说find默认执行的是精确匹配基于 queryHash 的哈希比较见 packages/query-core/src/utils.ts 中matchQuery对exact的处理这与findAll默认的部分前缀匹配语义形成鲜明对比。queryCache.findAll按筛选条件获取查询实例集合findAll是更进一步的同步方法用于从缓存中获取部分匹配查询键的查询实例。如果没有任何查询匹配则返回空数组。const queries queryCache.findAll({ queryKey })参数说明filters?: QueryFilters可选完整定义见 Query Filters。不传时返回缓存中全部查询。返回值Query[]缓存中所有匹配的查询实例数组。QueryFilters 支持的全部字段根据 packages/query-core/src/utils.ts 的类型定义与 docs/framework/react/guides/filters.md筛选器支持以下属性属性类型说明queryKeyQueryKey \| TuplePrefixesQueryKey设置要匹配的查询键exactboolean若设为true只返回查询键完全一致的查询否则按前缀/部分匹配typeactive \| inactive \| all默认allactive匹配活跃查询inactive匹配非活跃查询stalebooleantrue匹配过期查询false匹配新鲜查询fetchStatusFetchStatusfetching匹配正在请求的查询paused匹配想请求但被暂停的查询idle匹配未在请求的查询predicate(query: Query) boolean自定义谓词函数作为最终过滤条件若未指定其他筛选器则对缓存中每个查询求值源码级原理空筛选返回全部findAll的实现非常直接——遍历全部查询用matchQuery逐个过滤// packages/query-core/src/queryCache.ts findAll(filters: QueryFiltersany {}): ArrayQuery { const queries this.getAll() return Object.keys(filters).length 0 ? queries.filter((query) matchQuery(filters, query)) : queries }注意当传入空对象{}或完全不传时它直接返回全部查询不做匹配计算。实测行为验证来自仓库测试queryCache.test.tsx 中有一个非常全面的findAll过滤测试可以作为行为参考findAll({ queryKey: key1 })返回且仅返回 key1 对应的查询由于 v4 起查询键必须是数组额外再包一层数组findAll({ queryKey: [key1] })会得到空数组findAll()与findAll({})返回全部 4 个查询type: inactive/type: active可按活跃状态过滤无活跃 observer 的查询为 inactivestale: true/stale: false可按是否过期过滤exact: true与exact: false默认控制查询键是精确匹配还是部分匹配——例如用{ a: a }部分匹配[{ a: a, b: b }]时exact: true返回空exact: false能命中predicate: (query) query query3可用自定义谓词精确挑选fetchStatus: idle/fetchStatus: fetching可按请求状态过滤。实战用 subscribe findAll 实现缓存上限控制仓库测试中展示了一个非常实用的模式——通过subscribe监听added事件配合findAll限制缓存大小只保留最近 2 个查询const testCache new QueryCache() const unsubscribe testCache.subscribe((event) { if (event.type added) { if (testCache.getAll().length 2) { testCache .findAll({ type: inactive, predicate: (q) q ! event.query, }) .forEach((query) { testCache.remove(query) }) } } }) const testClient new QueryClient({ queryCache: testCache })完整代码见 queryCache.test.tsx。该用例最终验证缓存中只剩最新添加的data3说明事件驱动 过滤 移除是可行的自定义缓存治理方案。queryCache.subscribe订阅整个缓存的更新事件subscribe方法用于订阅整个查询缓存并收到缓存发生的安全/已知更新通知例如查询状态改变、查询被新增、更新或移除。const callback (event) { console.log(event.type, event.query) } const unsubscribe queryCache.subscribe(callback)参数与返回值callback: (event: QueryCacheNotifyEvent) void每当缓存通过其受追踪的更新机制如query.setState、queryClient.removeQueries等被更新时该函数都会被调用。对缓存进行的计划外out of scope变更是不被鼓励的且不会触发订阅回调。返回值unsubscribe: Function void调用它即可取消订阅。事件类型源码定义根据 queryCache.ts 中QueryCacheNotifyEvent的联合类型定义订阅者可能收到以下 7 种事件事件 type携带负载含义addedquery查询被加入缓存removedquery查询被移出缓存updatedquery,action查询状态被更新携带触发更新的 ActionobserverAddedquery,observer有新 observer 订阅该查询observerRemovedquery,observer有 observer 取消订阅该查询observerResultsUpdatedqueryobserver 的结果被更新observerOptionsUpdatedquery,observerobserver 的选项被更新源码级原理notify 与批量通知所有通知最终都汇聚到notify方法它使用notifyManager.batch将同一批次内的多次通知合并执行避免中间状态导致重复渲染// packages/query-core/src/queryCache.ts notify(event: QueryCacheNotifyEvent): void { notifyManager.batch(() { this.listeners.forEach((listener) { listener(event) }) }) }notifyManager的实现见 packages/query-core/src/notifyManager.ts它的批量调度机制是 TanStack Query 高性能通知体系的核心。一个查询完整生命周期中产生的事件序列仓库测试 queryCache.test.tsx 记录了一个查询从创建到过期的完整事件序列1. added // 查询加入缓存 - loading 2. observerResultsUpdated // observer 结果更新 - loading 3. observerAdded // observer 加入 4. observerResultsUpdated // observer 结果更新 - fetching 5. updated // 查询状态更新 - fetching 6. observerResultsUpdated // observer 结果更新 - success 7. updated // 查询状态更新 - success 8. observerResultsUpdated // observer 结果更新 - stale该测试还断言了事件序列中出现的event.query始终是同一个缓存实例验证了缓存实例的唯一性。queryCache.clear清空整个缓存clear方法用于完全清空缓存重新开始。queryCache.clear()从源码看clear会遍历所有查询并逐个remove且整个过程被包裹在notifyManager.batch中保证只触发一次批量通知// packages/query-core/src/queryCache.ts clear(): void { notifyManager.batch(() { this.getAll().forEach((query) { this.remove(query) }) }) }而remove内部会先调用query.destroy()释放 observer 等资源再从 Map 中删除并发出removed事件见 queryCache.ts。注意remove的删除是按实例校验的只有当前存储在该 queryHash 下的实例才会被真正删除这一点在测试 queryCache.test.tsx 中有明确验证。源码纵览QueryCache 的完整内部机制为了更深入地理解这里梳理 QueryCache 除公开方法外的几个关键内部成员均位于 packages/query-core/src/queryCache.tsbuild(client, options, state?)核心的查询构建入口。它先用options.queryHash ?? hashQueryKeyByOptions(queryKey, options)计算哈希可自定义queryKeyHashFn默认使用 utils.ts 中的hashKey——基于 JSON 序列化并对普通对象按键排序的稳定哈希若缓存中已存在同哈希查询则直接复用否则创建新的Query并add进缓存。这保证了同一查询键在缓存中只有一个实例。get(queryHash)/getAll()分别返回单个查询按哈希与全部查询数组find/findAll均基于getAll实现。add(query)仅当哈希不存在时才写入 Map 并发出added事件重复添加同一哈希的查询不会生效测试 queryCache.test.tsx 验证了缓存长度仍为 1。onFocus()/onOnline()批量地将窗口聚焦、网络恢复事件转发给缓存内的每个查询用于驱动重新聚焦即刷新恢复在线即刷新等默认行为配合refetchOnWindowFocus、refetchOnReconnect选项。QueryStore内部存储接口抽象了has/set/get/delete/values操作目前以原生Mapstring, Query实现键为queryHash。一条查询数据的完整流转链路把以上机制串起来可以画出一次典型查询请求的完整调用链useQuery / queryClient.query │ ▼ QueryClient ──► queryCache.build(client, options) // 计算 queryHash命中缓存或新建 Query │ // (queryCache.ts L100-L131) ▼ Query.fetch / setState ──► 缓存内查询状态更新 │ ├──► cache.config.onSuccess / onError / onSettled // 全局回调 (query.ts L572-L616) │ └──► cache.notify({ type: updated, ... }) // 通知订阅者 (queryCache.ts L200-L206) │ ▼ QueryObserver ──► 框架层 (React/Vue/Svelte/Solid/Angular...) ──► UI 更新这个链路说明QueryClient是面向开发者的门面QueryCache是实际的数据仓库与事件源Query是存储的最小单元QueryObserver则是连接缓存与 UI 的桥梁。进一步阅读要更深入地理解 QueryCache 的内部工作原理官方推荐阅读 TkDodo 的《Inside React Query》系列文章见原文档 Further reading 部分。想了解查询键的哈希与匹配规则可阅读 Query Keys 指南 与 Filters 指南。想了解与 QueryCache 平行的变更缓存可阅读 MutationCache 参考文档。想直接阅读源码与测试可前往 packages/query-core/src/queryCache.ts 与 packages/query-core/src/tests/queryCache.test.tsx以及依赖的 Query、QueryClient、notifyManager、subscribable、utils 等核心模块。小结QueryCache 是整个 TanStack Query 生态的地基它用 Map 统一管理所有查询实例通过find/findAll提供精确与模糊两种查询检索能力通过subscribe将added/removed/updated/observer*等 7 类事件广播给监听者通过clear一键重置并借助onSuccess/onError/onSettled提供全局生命周期钩子。日常开发中你几乎不会直接触碰它——但当你需要做缓存预取判断、自定义缓存淘汰策略、全局错误上报或深度调试查询状态时理解并善用 QueryCache 将成为你手中最有力的武器。【免费下载链接】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),仅供参考