ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

TanStack Query(Angular)查询失效实战:用 invalidateQueries 精确标记过期数据并触发后台刷新

TanStack Query(Angular)查询失效实战:用 invalidateQueries 精确标记过期数据并触发后台刷新 TanStack QueryAngular查询失效实战用 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本篇围绕 TanStack Query 的 Angular 适配包tanstack/angular-query-experimental讲解查询失效Query Invalidation机制当你知道用户操作已使某些数据过期时如何通过QueryClient.invalidateQueries按前缀、精确匹配或谓词函数标记查询为过期并默认对活跃查询触发后台刷新。文中结合query-core源码给出invalidateQueries的完整执行链路与过滤匹配规则帮助你在 Angular 应用中实现精准、可控的缓存失效。为什么需要主动失效查询依赖staleTime等待查询自然过期再重新拉取并不能覆盖所有场景——尤其是当你明确知道某个查询的数据已经因为用户的操作提交表单、删除条目、切换筛选条件等而过期时。为此QueryClient提供了invalidateQueries方法让你可以智能地把查询标记为过期stale并在必要时立即触发后台刷新。说明一些使用规范化缓存的库会试图以命令式方式或依赖 schema 推断来用新数据更新本地查询TanStack Query 的做法是避免维护规范化缓存的繁琐工作转而提供定向失效、后台重新拉取以及原子更新的工具。基础用法在 Angular 组件中获取 QueryClient 并失效查询Angular 版本通过依赖注入获取QueryClient实例它由tanstack/angular-query-experimental的 provider 体系注册为 DI token然后用injectQuery声明查询。以下示例同时展示了「失效所有查询」和「按 key 前缀失效」两种最基本的用法import { inject } from angular/core import { injectQuery, QueryClient } from tanstack/angular-query-experimental class MyComponent { queryClient inject(QueryClient) // 失效缓存中的每一个查询 invalidateAll() { this.queryClient.invalidateQueries() } // 失效所有 queryKey 以 todos 开头的查询 invalidateTodos() { this.queryClient.invalidateQueries({ queryKey: [todos] }) } todoListQuery injectQuery(() ({ queryKey: [todos], queryFn: fetchTodoList, })) }当查询被invalidateQueries命中后会发生两件事该查询被标记为过期。这个过期状态会覆盖你在injectQuery选项中配置的staleTime如果该查询当前正被injectQuery等函数渲染即处于 active 状态它还会立即在后台重新拉取一次。官方文档中给出的完整类示例docs/framework/angular/guides/query-invalidation.md对应内容如下两条查询都会被[todos]前缀命中import { injectQuery, QueryClient } from tanstack/angular-query-experimental class QueryInvalidationExample { queryClient inject(QueryClient) invalidateQueries() { this.queryClient.invalidateQueries({ queryKey: [todos] }) } // Both queries below will be invalidated todoListQuery injectQuery(() ({ queryKey: [todos], queryFn: fetchTodoList, })) todoListQuery injectQuery(() ({ queryKey: [todos, { page: 1 }], queryFn: fetchTodoList, })) }失效背后的源码执行链路invalidateQueries的实现位于 queryClient.ts核心逻辑可以概括为三步invalidateQueries( filters?: InvalidateQueryFilters, 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, ) }) }三个关键点批量通知整个流程包裹在notifyManager.batch中避免每次状态变更都触发一次同步渲染通知标记过期命中的每个Query实例都会调用invalidate()其实现见 query.ts——仅当尚未标记时派发一次invalidateactionreducer 将状态置为isInvalidated: truequery.ts默认只刷新 active 查询随后调用refetchQueriestype默认为active也就是说只有当前有订阅者正在渲染的查询会被后台拉取如果你希望连 inactive 查询一起刷新可以通过refetchType显式指定见下文类型定义。isInvalidated标志如何影响过期判断见 query.ts 中的isStaleByTimeisStaleByTime(staleTime: StaleTime 0): boolean { // no data is always stale if (this.state.data undefined) { return true } // static is never stale if (staleTime static) { return false } // if the query is invalidated, it is stale if (this.state.isInvalidated) { return true } return !timeUntilStale(this.state.dataUpdatedAt, staleTime) }从源码结构看isInvalidated的优先级高于staleTime的时间计算仅在staleTime static的永久新鲜场景下例外。这意味着即使你配置了很长的staleTime调用invalidateQueries之后查询也会立刻变成 stale进而在满足条件时重新拉取。而实际的重新拉取发生在 queryClient.ts 的refetchQueries中它对命中的查询逐一调用query.fetch跳过disabled与static查询并且对fetchStatus paused通常由 offline 插件造成的查询直接返回已解析的 Promise不会发起请求。查询匹配规则前缀、精确与谓词invalidateQueries接受一个InvalidateQueryFilters继承自QueryFilters其完整定义见 types.tsexport interface InvalidateQueryFilters TQueryKey extends QueryKey QueryKey, extends QueryFiltersTQueryKey { refetchType?: QueryTypeFilter | none }常用字段包括字段说明queryKey按 key 匹配默认做前缀部分匹配配合格式化 key 可命中一整族查询exact为true时要求 key 完全一致按哈希比较不匹配子 keypredicate谓词函数接收缓存中的每个Query实例返回true才命中提供最高粒度type按 active / inactive / all 过滤invalidateQueries默认按 active 刷新refetchType控制刷新阶段的type取QueryTypeFilter或nonenone表示只标记过期、不触发任何刷新stale/fetchStatus按当前过期状态或抓取状态过滤更多过滤器用法可参考 Angular 侧的 Query Filters 指南。这些过滤条件最终都汇入 utils.ts 中的matchQueryif (queryKey) { if (exact) { if (query.queryHash ! hashQueryKeyByOptions(queryKey, query.options)) { return false } } else if (!partialMatchKey(query.queryKey, queryKey)) { return false } } // ... type / stale / fetchStatus / predicate 依次校验1. 前缀匹配默认行为不传exact时invalidateQueries使用partialMatchKey做前缀匹配。它的规则实现在 utils.ts数组按索引逐项比较过滤 key 比查询 key 短时视为前缀匹配对象只要求过滤对象中出现的字段在查询 key 中逐一相等因此对象字段顺序不同也不影响命中。queryClient.invalidateQueries({ queryKey: [todos] }) // 以下两个查询都会被失效 todoListQuery injectQuery(() ({ queryKey: [todos], queryFn: fetchTodoList, })) todoListQuery injectQuery(() ({ queryKey: [todos, { page: 1 }], queryFn: fetchTodoList, }))2. 带更具体变量的匹配把更具体的 queryKey 传给invalidateQueries可以只命中携带该变量的查询queryClient.invalidateQueries({ queryKey: [todos, { type: done }], }) // The query below will be invalidated todoListQuery injectQuery(() ({ queryKey: [todos, { type: done }], queryFn: fetchTodoList, })) // However, the following query below will NOT be invalidated todoListQuery injectQuery(() ({ queryKey: [todos], queryFn: fetchTodoList, }))注意方向性过滤 key 必须是查询 key 的“前缀子集”。[todos, { type: done }]不能命中更“短”的[todos]这与上面前缀匹配的方向一致——匹配是查询 key 包含过滤 key而不是反过来。3. exact只命中没有子 key 的查询如果你只想失效不带任何额外变量或子 key的todos查询传exact: truequeryClient.invalidateQueries({ queryKey: [todos], exact: true, }) // The query below will be invalidated todoListQuery injectQuery(() ({ queryKey: [todos], queryFn: fetchTodoList, })) // However, the following query below will NOT be invalidated const todoListQuery injectQuery(() ({ queryKey: [todos, { type: done }], queryFn: fetchTodoList, }))从源码看exact模式下不走partialMatchKey而是直接比较query.queryHash与hashQueryKeyByOptions(queryKey, query.options)utils.ts即按 key 的哈希做全等判断天然排除了带子 key 的查询。4. predicate最高粒度的谓词匹配当你需要比 key 结构更复杂的条件比如按 key 中某个数值字段的大小过滤可以传入predicate函数。它会接收缓存中的每个Query实例返回true才失效该查询queryClient.invalidateQueries({ predicate: (query) query.queryKey[0] todos query.queryKey[1]?.version 10, }) // The query below will be invalidated todoListQuery injectQuery(() ({ queryKey: [todos, { version: 20 }], queryFn: fetchTodoList, })) // The query below will be invalidated todoListQuery injectQuery(() ({ queryKey: [todos, { version: 10 }], queryFn: fetchTodoList, })) // However, the following query below will NOT be invalidated todoListQuery injectQuery(() ({ queryKey: [todos, { version: 5 }], queryFn: fetchTodoList, }))在 utils.ts 中predicate是matchQuery的最后一道校验if (predicate !predicate(query)) return false因此它可以与queryKey、exact、type等条件叠加使用。5. refetchType只标记过期而不刷新默认情况下invalidateQueries会对命中的 active 查询发起后台刷新若你只希望“标记为过期”例如延迟到用户回到页面时再拉取可以传refetchType: none。从 queryClient.ts 看此时方法在标记完成后直接return Promise.resolve()跳过refetchQueries阶段。该 Promise 的解析时机与底层刷新行为取消/并发策略由RefetchOptions如cancelRefetch、throwOnError控制可在 types.ts 中查看默认值说明。Angular 侧的获取方式与注意事项推荐通过 DI 获取文档示例统一使用inject(QueryClient)。仓库中保留的injectQueryClient辅助函数已标记为废弃见 inject-query-client.ts其 JSDoc 明确建议改用inject(QueryClient)需要自定义 injector 时用injector.get(QueryClient)查询声明用injectQueryAngular 版以“函数”inject function取代 React 版的 hooks选项以惰性函数传入便于在信号signal上下文中响应式地依赖其他信号值构造 key失效是幂等且批量的invalidate()内部会先检查isInvalidated避免重复派发而invalidateQueries全程运行在notifyManager.batch中对含大量查询的应用来说这意味着一次失效操作只会触发一次批量通知配合官方示例理解上下文仓库中 examples/angular/optimistic-updates 与 examples/angular/auto-refetching 等示例工程展示了失效与变更、自动刷新组合使用的完整可运行工程适合作为上述机制的对照阅读。小结invalidateQueries是 TanStack Query 中“定向失效”的核心 API它先用queryKey前缀或exact精确与predicate谓词在缓存中筛选查询再统一打上isInvalidated标记覆盖staleTime最后默认对 active 查询发起一次后台刷新可用refetchType: none关闭刷新。在 Angular 应用中你只需通过inject(QueryClient)拿到客户端实例即可在组件或服务的任意生命周期钩子如变更操作成功后调用它完成精确到单个变量、甚至按业务谓词的缓存失效与数据同步。【免费下载链接】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),仅供参考
返回列表