Solid Query 查询失效指南:invalidateQueries 的精确匹配、后台重取与底层实现

发布时间:2026/9/10 16:16:10
Solid Query 查询失效指南:invalidateQueries 的精确匹配、后台重取与底层实现 Solid Query 查询失效指南invalidateQueries 的精确匹配、后台重取与底层实现【免费下载链接】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查询失效Query Invalidation是 TanStack Query 系列中主动管理服务器状态一致性的核心手段。本指南面向使用tanstack/solid-query的开发者讲解当用户操作导致缓存数据必然过期时如何通过QueryClient.invalidateQueries将查询标记为 stale、按需触发后台重新拉取并利用前缀匹配、exact与predicate实现从“全部失效”到“单条精确失效”的粒度控制。读完本文你将掌握失效 API 的完整用法并能结合源码理解其内部调用链与 refetch 的默认行为。说明本文对应的文档docs/framework/solid/guides/query-invalidation.md本身是一份共享指南——它通过 frontmatter 中的ref: docs/framework/react/guides/query-invalidation.md指向 React 框架下的同主题文档并依赖replace映射自动完成tanstack/react-query→tanstack/solid-query、useQuery(→useQuery(() 等改写再交由 scripts/generate-docs.ts 这类构建流程生成面向 Solid 用户的版本。因此下文所有语义与示例同样适用于该指南的完整渲染结果且均已结合本仓库 Solid Query 与 query-core 的源码进行核对。为什么需要显式失效staleTime 并非万能TanStack Query 默认的策略是“数据过期后再拉取”refetch on stale。但等待查询自然过期并不总能在正确的时机触发重新拉取——尤其当你在代码层面明确知道某条数据已经因为用户操作而过期时例如新增了一条 todo、切换了筛选条件你希望立刻让它失效。针对这种场景QueryClient提供了invalidateQueries方法它能在“不手动维护归一化缓存”的前提下智能地把匹配到的查询标记为 stale并在合适时对它们发起后台 refetch// 让缓存中的每一个查询都失效 queryClient.invalidateQueries() // 让所有 queryKey 以 todos 开头的查询失效 queryClient.invalidateQueries({ queryKey: [todos] })这里正是 TanStack Query 与其他采用归一化缓存的库在哲学上的分水岭其他库会尝试“命令式”或“通过 schema 推断”地拿新数据更新本地查询而 TanStack Query 交给你的工具链建议的是定向失效targeted invalidation→ 后台重取background-refetching→ 原子更新atomic updates从而省去维护归一化缓存的大量手工劳动。Solid Query 完全继承了这一套核心机制——tanstack/solid-query通过export * from tanstack/query-core将QueryClient及其方法原样对外暴露见 packages/solid-query/src/index.ts。失效后会发生什么两步行为调用invalidateQueries命中一个查询时会发生两件事它被标记为 stale。这个 stale 状态会覆盖useQuery及相关函数中配置的任何staleTime——也就是说即便你给查询设置了很长的staleTime一次失效也会让它立刻处于过期状态如果该查询当前正在被渲染即存在活跃观察者它还会在后台被重新拉取background refetchUI 不会因此进入 loading 闪烁。从核心实现可以精确验证这两步行为。invalidateQueries定义在 packages/query-core/src/queryClient.ts#L298-L318invalidateQueriesTTaggedQueryKey extends QueryKey QueryKey( filters?: InvalidateQueryFiltersTTaggedQueryKey, options: InvalidateOptions {}, ): Promisevoid { return notifyManager.batch(() { this.#queryCache.findAll(filters).forEach((query) { query.invalidate() }) if (filters?.refetchType none) { return Promise.resolve() } return this.refetchQueries( { ...filters, type: filters?.refetchType ?? filters?.type ?? active, }, options, ) }) }调用链可以归纳为queryCache.findAll(filters)先按过滤器收集匹配的查询 → 对每个匹配调用query.invalidate()完成 stale 标记 → 再进入refetchQueries按type决定重取哪些查询。默认情况下type回退为active这正是“当前正在被渲染的查询才会被重取”这一行为的上游来源若显式传入refetchType: none则只标记 stale、完全不触发网络请求。在 Solid 场景下“正在被渲染”可以理解为组件仍存活、观察者对查询保有订阅Solid Query 的useQuery底层通过createMemo包装 options 并驱动 Observer见 packages/solid-query/src/useQuery.ts。同一批操作被包裹在notifyManager.batch中意味着多次匹配的失效与重取会被合并成一次通知。获取 QueryClientProvider 与 useQueryClient所有invalidateQueries调用都发生在QueryClient实例上。在组件内最常规的做法是通过上下文拿到它import { QueryClientProvider, useQueryClient, useQuery } from tanstack/solid-query import { QueryClient } from tanstack/solid-query const queryClient new QueryClient() function App() { return ( QueryClientProvider client{queryClient} Todos / /QueryClientProvider ) }QueryClientProvider与useQueryClient在 packages/solid-query/src/QueryClientProvider.tsx 中实现组件树内任意位置都能拿到同一份 client。然后便可以在事件处理器、effect 或 mutation 的onSuccess回调里执行失效function Todos() { const queryClient useQueryClient() const handleSave async () { await saveTodo(...) queryClient.invalidateQueries({ queryKey: [todos] }) } }Query Matching按前缀匹配多个查询invalidateQueries以及removeQueries等其他支持部分匹配的 API可以对多个查询按前缀批量匹配也可以传更具体的 key 去精确匹配某一个查询。可用的过滤器种类与语义详见 Query Filters 文档其中queryKey、exact、predicate、type等过滤器在失效场景的取值会在下文结合源码逐一说明。在 Solid 中options 以响应式函数形式传入useQuery类型定义为UseQueryOptions AccessorQueryOptions见 packages/solid-query/src/types.ts。先看前缀匹配——以todos前缀失效所有列表查询import { useQuery, useQueryClient } from tanstack/solid-query // 从上下文中获取 QueryClient const queryClient useQueryClient() queryClient.invalidateQueries({ queryKey: [todos] }) // 下面两个查询都会被失效 const todoListQuery useQuery(() ({ queryKey: [todos], queryFn: fetchTodoList, })) const todoListQueryWithPage useQuery(() ({ queryKey: [todos, { page: 1 }], queryFn: fetchTodoList, }))之所以“前缀命中”是因为查询键匹配在queryCache.findAll中基于确定性的 key 哈希与包含关系进行判断——[todos]与[todos, { page: 1 }]共享todos前缀。因此合理设计查询键参见 Query Keys是精准失效的前提规范化、层级化的 key 才能让一条失效语句覆盖一组相关查询。更精确的变量匹配传入更具体的 key当你只想失效携带特定变量的子查询时把更完整的 key 传给invalidateQueries即可queryClient.invalidateQueries({ queryKey: [todos, { type: done }], }) // 下面这个查询会被失效 const todoListQuery useQuery(() ({ queryKey: [todos, { type: done }], queryFn: fetchTodoList, })) // 但下面这个查询不会被失效key 与过滤器不匹配 const todoListQuery useQuery(() ({ queryKey: [todos], queryFn: fetchTodoList, }))注意此处的匹配仍基于前缀规则[todos, { type: done }]既命中完全一致的 key也不会向下误伤[todos]但假如存在[todos, { type: done }, { page: 1 }]它同样会被命中。只想命中“没有更多变量”的查询exact: true如果诉求恰好相反——只失效恰好等于[todos]、没有任何额外变量或子 key 的那条查询——就传入exact: truequeryClient.invalidateQueries({ queryKey: [todos], exact: true, }) // 下面这个查询会被失效 const todoListQuery useQuery(() ({ queryKey: [todos], queryFn: fetchTodoList, })) // 但下面这个查询不会被失效 const todoListQuery useQuery(() ({ queryKey: [todos, { type: done }], queryFn: fetchTodoList, }))exact让过滤器从“key 是过滤 key 的超集/同前缀”收紧为“key 必须与过滤 key 完全相等”适用于批量列表中需要单独刷新列表首屏、但不动带筛选条件子查询的场景。需要更高自由度的过滤predicate 函数如果前缀与exact都无法表达你的过滤逻辑invalidateQueries还支持传入谓词函数。该函数会依次收到来自查询缓存的每一个Query实例你通过返回true/false决定是否令其失效queryClient.invalidateQueries({ predicate: (query) query.queryKey[0] todos query.queryKey[1]?.version 10, }) // 下面这个查询会被失效 const todoListQuery useQuery(() ({ queryKey: [todos, { version: 20 }], queryFn: fetchTodoList, })) // 下面这个查询会被失效 const todoListQuery useQuery(() ({ queryKey: [todos, { version: 10 }], queryFn: fetchTodoList, })) // 但下面这个查询不会被失效 const todoListQuery useQuery(() ({ queryKey: [todos, { version: 5 }], queryFn: fetchTodoList, }))谓词让你可以基于任意查询维度如版本号阈值、时间戳、自定义字段做判断是queryKey/exact之外的兜底过滤手段。从实现上可以看到predicate被合并进 filters 后交由findAll统一消费因此它不仅能用于失效也适用于removeQueries、refetchQueries等同族 API语义完全一致。失效的粒度参数与 refetch 行为综合上述示例与 queryClient.ts 的实现invalidateQueries(filters, options)两个入参的可控点可归纳为参数位置作用说明queryKeyfilters按 key前缀圈定查询匹配沿用查询键的确定性哈希与包含规则exactfilters让 key 匹配变为精确相等默认falsepredicatefilters自定义逐条判断收到每个Query实例返回布尔值type/refetchTypefilters控制重取范围可取active、inactive、all、none默认为activenone表示只标记 stale 不重取cancelRefetchoptions是否在重取前取消进行中的请求默认truerefetchQueries中由options.cancelRefetch ?? true兜底throwOnErroroptions后台重取失败时是否抛错默认为不抛出、吞掉错误源码中.catch(noop)兜底需要补充的关键实现细节在refetchQueries内部见 packages/query-core/src/queryClient.ts真正参与 fetch 的查询还会再经过一层过滤——!query.isDisabled() !query.isStatic()即被禁用disabled或标记为静态的查询即使被匹配也不会触发网络重取只会停留在 stale 状态等待后续激活。同时notifyManager.batch包裹保证了一次失效覆盖多个查询时只产生一次状态通知避免 Solid 端不必要的重复渲染。与 Mutation 协同推荐的失效位置invalidateQueries最常见的实战场景是配合 mutation 使用在写操作成功onSuccess后失效相关读查询让列表/详情自动回到最新状态。仓库的 Solid 框架下有两份紧邻的专题指南可供进一步深入Invalidations from Mutations在 mutation 成功后失效查询的推荐写法与顺序Updates from Mutation Responses当 mutation 直接返回新数据时用setQueryData做原子更新以减少一次网络往返Optimistic Updates先乐观渲染、失败再回滚的进阶一致性方案。这三者与invalidateQueries共同构成了 TanStack Query 推荐的“数据写入后一致性”工具箱按场景取舍能原子更新就直接更新不能或不想维护细节就用定向失效让系统自己补齐数据。小结当用户行为让数据“注定过期”时用invalidateQueries显式失效而非等待staleTime自然过期一次失效 标记 stale覆盖任何staleTime 默认对活跃正在渲染的查询后台 refetch匹配粒度可按需递进全局 → 前缀queryKey→ 更具体的 key →exact: true→predicate谓词想要“只失效不请求”传refetchType: none想让禁用/静态查询也参与需要注意它们默认不会触发 fetch所有匹配与重取逻辑都沉淀在query-core的QueryClient/QueryCache中Solid Query 通过tanstack/solid-query完全复用同一套实现因此行为在 React、Solid、Vue、Svelte 各框架间保持一致本文对应的完整实现在 docs/framework/solid/guides/query-invalidation.md 及其 React 源文档。【免费下载链接】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),仅供参考