首页
/ 深入解析 Angular Query 的 QueryFeatures 类型别名与特性(Feature)扩展机制

深入解析 Angular Query 的 QueryFeatures 类型别名与特性(Feature)扩展机制

2026-09-07 09:46:43作者:秋阔奎Evelyn

TanStack Query 的 Angular 实现(@tanstack/angular-query-experimental)通过一个统一的入口函数 provideTanStackQuery 来装配全局依赖,而扩展能力则由 QueryFeatures 这一类型别名所代表的 Feature 体系承载。本文以仓库中的类型别名文档(QueryFeatures.md)为核心,结合 providers.tswith-devtools.ts 等源码实现,讲清楚 Feature 的类型契约、两条具体特性(开发者工具与持久化)的启用方式与底层原理,让你在业务中能够按需组合、安全扩展 Angular Query 的能力。

QueryFeatures 是什么

providers.ts 中,QueryFeatures 被定义为一个联合类型别名:

export type QueryFeatures = DevtoolsFeature | PersistQueryClientFeature

按原文档的说明,它代表了"所有可供 provideTanStackQuery 使用的 Query 特性"。特性通过向 provideTanStackQuery 调用追加特殊的函数来启用,例如 withDevtools()withPersistQueryClient(),每个函数对应一个文档中已定义好的符号(Symbol)。当前版本只暴露了两种特性:

类型成员 对应的启用函数 功能定位
DevtoolsFeature withDevtools() 在开发模式加载 TanStack Query Devtools 面板
PersistQueryClientFeature withPersistQueryClient() QueryClient 的缓存持久化并在应用启动时恢复

从源码结构看,这个联合类型并非固定写死,而是由内部常量数组 queryFeatures = ['Devtools', 'PersistQueryClient'] as const 推导出的 QueryFeatureKind 所约束(见 providers.ts),今后新增特性时只需补充对应的 kind 与类型成员即可。

Feature 的底层类型契约:QueryFeature 与 queryFeature

要理解 QueryFeatures,先看支撑它的两个内部声明。其一是"特性对象"的类型契约:

export interface QueryFeature<TFeatureKind extends QueryFeatureKind> {
  ɵkind: TFeatureKind
  ɵproviders: Array<Provider>
}

也就是说,每一个 Feature 本质上是一个带有 ɵkind(特性种类标记)与 ɵproviders(要向 Angular DI 注入的 Provider 数组)的普通对象,刻意使用 ɵ 前缀标识其为框架内部结构。DevtoolsFeaturePersistQueryClientFeature 分别是 QueryFeature<'Devtools'>QueryFeature<'PersistQueryClient'> 的别名。

其二是构造这种对象的辅助工厂函数(见 providers.ts):

export function queryFeature<TFeatureKind extends QueryFeatureKind>(
  kind: TFeatureKind,
  providers: Array<Provider>,
): QueryFeature<TFeatureKind> {
  return { ɵkind: kind, ɵproviders: providers }
}

provideTanStackQuery 之所以能接受"一个 QueryClient + 若干特性",正是因为其签名把它声明为可变参数:

export function provideTanStackQuery(
  queryClient: QueryClient | InjectionToken<QueryClient>,
  ...features: Array<QueryFeatures>
): Array<Provider>

函数体把两部分组装起来返回给 Angular:

return [
  provideQueryClient(queryClient),
  features.map((feature) => feature.ɵproviders),
]

可以看到整个流程:withDevtools() / withPersistQueryClient() 各返回一个 QueryFeatures 对象 → 在 provideTanStackQuery 内被解包成各自的 ɵproviders → 与基础 QueryClient Provider 一起作为扁平化依赖交给应用。这与文档中"Features can be enabled by adding special functions to the provideTanStackQuery call"的描述完全对应。

特性一:DevtoolsFeature 与 withDevtools

DevtoolsFeaturewithDevtools 函数的返回类型。其 API 签名(见 devtools/types.ts)为:

export type WithDevtools = (
  withDevtoolsFn?: WithDevtoolsFn,
  options?: WithDevtoolsOptions,
) => DevtoolsFeature

最简单的启用方式是把它作为第二个参数追加进 provideTanStackQuery

import {
  provideTanStackQuery,
  QueryClient,
} from '@tanstack/angular-query-experimental'
import { withDevtools } from '@tanstack/angular-query-experimental/devtools'

bootstrapApplication(AppComponent, {
  providers: [
    provideTanStackQuery(new QueryClient(), withDevtools()),
  ],
})

默认只在开发模式加载

withDevtools 的实现位于 with-devtools.ts。它通过 queryFeature('Devtools', [...]) 返回两个 Provider:

  1. 一个 DEVTOOLS_OPTIONS_SIGNAL(内部 InjectionToken<Signal<DevtoolsOptions>>),把传入的 withDevtoolsFn 结果包成 computed 信号;
  2. 一个 ENVIRONMENT_INITIALIZERmulti: true),在应用环境初始化时执行 Devtools 装配逻辑。

装配逻辑中最关键的是"是否加载"的计算(对应 DevtoolsOptions.loadDevtools):

const shouldLoadToolsSignal = computed(() => {
  const { loadDevtools } = devtoolsOptions()
  return typeof loadDevtools === 'boolean'
    ? loadDevtools
    : isDevMode()
})

即默认行为是"仅在 Angular 开发模式(isDevMode())下加载",对应官方文档中 "By default the tools will then be loaded when your app is in development mode" 的约定。可配置的选项(见 types.ts)包括:

  • loadDevtools?: 'auto' | boolean——auto(默认)随开发模式懒加载;true/false 可强制覆盖,例如在"以生产模式运行但想打开工具"的测试环境;
  • initialIsOpen?: boolean——是否默认展开面板;
  • position?: 'top' | 'bottom' | 'left' | 'right'——面板位置,默认 bottom
  • buttonPosition?: DevtoolsButtonPosition——开关按钮位置,默认 bottom-right
  • theme?: 'light' | 'dark' | 'system'——面板主题,默认 system
  • client?: QueryClient——自定义使用的 QueryClient 实例;
  • errorTypes?: Array<DevtoolsErrorType>——自定义可在 Devtools 中展示的错误类型;
  • styleNonce?: string——配合 CSP 的 nonce;
  • shadowDOMTarget?: ShadowRoot——把面板样式挂载到指定 DOM;
  • hideDisabledQueries?: boolean——隐藏禁用查询。

动态注入依赖

withDevtools 还支持通过第二参数 { deps: [...] } 声明依赖,并让第一参数回调接收它们(如 types.ts 中的示例,把自定义 DevtoolsOptionsManager 注入后再决定 loadDevtools)。例如可结合服务、路由状态或快捷键对应的信号,让工具在运行期"按需开关"。真实仓库的 basic-persister 示例withDevtools() 与持久化特性被同时启用。

懒加载与 DOM 挂载细节

为避免打包膨胀,Devtools 本体是通过 import('@tanstack/query-devtools') 动态引入的(见 with-devtools.ts),随后实例化 TanstackQueryDevtools 并追加一个带 tsqd-parent-container class 的 div 到 document.body 再挂载面板;若该依赖缺失会输出 "Install @tanstack/query-devtools or reinstall without --omit=optional." 的提示。实现还通过 DEVTOOLS_PROVIDED 内部 token 防止在子注入器中重复装配。

特性二:PersistQueryClientFeature 与 withPersistQueryClient

PersistQueryClientFeature 被文档注解为"描述 withPersistQueryClient 函数返回值"的类型(见 providers.ts)。它对应的实现在另一个独立包 angular-query-persist-client 中,使用前需额外安装依赖。

启用持久化的完整配置

先创建 persister(持久化存储器),再把它以特性方式传入。仓库示例 app.config.ts 给出了一套生产可用的完整配置:

import { provideHttpClient, withFetch } from '@angular/common/http'
import {
  QueryClient,
  provideTanStackQuery,
} from '@tanstack/angular-query-experimental'
import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
import { withPersistQueryClient } from '@tanstack/angular-query-persist-client'
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'
import type { ApplicationConfig } from '@angular/core'

const localStoragePersister = createAsyncStoragePersister({
  storage: window.localStorage,
})

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withFetch()),
    provideTanStackQuery(
      new QueryClient({
        defaultOptions: {
          queries: {
            staleTime: 1000 * 60, // 1 minute
            gcTime: 1000 * 60 * 60 * 24, // 24 hours
          },
        },
      }),
      withDevtools(),
      withPersistQueryClient({
        persistOptions: {
          persister: localStoragePersister,
        },
      }),
    ),
  ],
}

withPersistQueryClient 的参数结构(见 with-persist-query-client.ts)为:

type PersistQueryClientOptions = {
  persistOptions: Omit<PersistQueryClientOptionsCore, 'queryClient'>
  onSuccess?: () => Promise<unknown> | unknown
  onError?: () => Promise<unknown> | unknown
}

其中 persistOptions 委托给 @tanstack/query-persist-client-corepersistQueryClient 的全部选项(仅剥离自动注入的 queryClient),onSuccess / onError 则在缓存恢复流程结束或失败时回调。

内部恢复流程

其实现同样先创建 isRestoring 信号并注入 provideIsRestoring(isRestoring.asReadonly())(应用可通过 injectIsRestoring() 感知"正在恢复旧缓存"这一状态),再通过 ENVIRONMENT_INITIALIZER 注册初始化任务(见 with-persist-query-client.ts):

  1. 仅在浏览器平台(isPlatformBrowser)执行,避免 SSR 阶段访问 localStorage 等浏览器 API;
  2. 调用 persistQueryClientRestore(options) 从存储中恢复缓存;成功触发 onSuccess,失败触发 onError
  3. finally 中将 isRestoring 置为 false,随后调用 persistQueryClientSubscribe(options) 订阅后续缓存变更并持续写回,最后通过 DestroyRef 在注入器销毁时清理订阅。

这里需要特别注意:由于缓存恢复是异步的,仓库示例把 gcTime 设为一整天、staleTime 设为一分钟,正是为了让持久化的旧数据在恢复后仍可被正常复用,是配合持久化特性的推荐调优。

两者的共性与取舍:何时需要特性参数

无论 withDevtools 还是 withPersistQueryClient,它们共享同一套内部协议(queryFeature(kind, providers) + ɵproviders 解包),这保证了 provideTanStackQuery 可以对传入的任意个特性做 features.map((feature) => feature.ɵproviders)统一扁平化处理,因此多个特性之间天然可组合、顺序无关。

几点工程建议:

  • Devtools 是"可选增强"而非必需:基础功能只需 provideTanStackQuery(new QueryClient());如文档所指,Devtools 默认只在开发模式懒加载,生产构建中不会带来额外运行时开销;
  • 持久化是独立包的能力PersistQueryClientFeature 类型由 angular-query-experimental 导出,但启用函数 withPersistQueryClient 需要安装 angular-query-persist-client,persister 则来自 @tanstack/query-async-storage-persister 等持久化包;
  • 按需分块加载:为 QueryClient 传入 InjectionToken 可使 TanStack Query 从主包中剔除、仅在懒加载路由中启用(见 providers.ts),但文档也指出"这是小优化,多数应用更推荐在根配置中直接提供 QueryClient"。

小结

QueryFeatures 类型别名是理解 Angular Query 扩展体系的钥匙:它把"可选的 Devtools 增强"与"缓存持久化"统一收编为强类型的特性对象,通过 provideTanStackQuery(queryClient, ...features) 这一单一入口完成注入。阅读本文后,你可以:用 withDevtools() 一行启用开发工具并按需定制位置与主题;用 withPersistQueryClient() 配合 async storage persister 实现刷新后缓存不丢失;更重要的是,当你需要进一步研究或自行扩展特性时,可以直接顺着类型定义进入 providers.ts,观察 queryFeatures 常量与 QueryFeature 契约,从而理解这类"声明式特性"在该仓库中如何被创建、装配与组合。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
858
1.35 K
docsdocs
暂无描述
Markdown
899
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
923
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.83 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
532
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
524
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
393