首页
/ TanStack Lit Query 中 IsFetchingAccessor 类型详解:useIsFetching 返回值的结构、用法与销毁机制

TanStack Lit Query 中 IsFetchingAccessor 类型详解:useIsFetching 返回值的结构、用法与销毁机制

2026-09-07 17:17:31作者:廉皓灿Ida

IsFetchingAccessor 是 TanStack Query 的 Lit 集成(@tanstack/lit-query)中 useIsFetching 工厂函数返回值的类型定义,位于 docs/framework/lit/reference/type-aliases/IsFetchingAccessor.md 对应的源码 packages/lit-query/src/useIsFetching.ts#L17。掌握它,你就理解了 Lit Query 中"计数类 API"的通用返回形态:一个既可调用、又可读取 .current 属性、还带有 destroy() 生命周期方法的响应式访问器。本文基于官方参考文档与仓库源码,完整拆解它的类型结构、使用方式、底层控制器实现与测试验证。

类型定义

官方参考文档给出的声明为:

type IsFetchingAccessor = ValueAccessor<number> & object;

仓库源码中的实际定义(packages/lit-query/src/useIsFetching.ts#L17):

export type IsFetchingAccessor = ValueAccessor<number> & { destroy: () => void }

两者语义一致:IsFetchingAccessorValueAccessor<number> 的交叉类型,并额外携带一个 destroy: () => void 成员。展开 ValueAccessor<number> 后,该类型等价于一个"可调用函数 + 只读 current 属性 + destroy 方法"的三元组合:

  • 可调用:直接调用 accessor() 即返回当前处于 fetching 状态、且匹配过滤条件的查询数量;
  • current 属性:以属性读取的方式获得同样的数值;
  • destroy():手动销毁底层控制器,断开 QueryCache 订阅并把控制器从宿主组件上移除。

ValueAccessor 是 Lit Query 中多个 API 共享的公共类型,定义在 packages/lit-query/src/accessor.ts#L32-L34

export type ValueAccessor<T> = (() => T) & {
  readonly current: T
}

它的实现通过 createValueAccessor 完成(packages/lit-query/src/accessor.ts#L36-L43):先创建一个包裹 getter 的函数,再用 Object.defineProperty 挂上 current 只读 getter,二者读取的是同一个数据源,因此 accessor()accessor.current 始终返回同值。这也是 IsFetchingAccessorValueAccessor<number> 交叉后仍声明为可调用对象的根本原因——函数本身就是访问器,current 只是同一个 getter 的别名。

useIsFetching 正是通过把 createValueAccessor 的结果与 destroy 方法合并来构造这个类型的实例(packages/lit-query/src/useIsFetching.ts#L147-L159):

export function useIsFetching(
  host: ReactiveControllerHost,
  filters: Accessor<QueryFilters> = {},
  queryClient?: QueryClient,
): IsFetchingAccessor {
  const controller = new IsFetchingController(host, filters, queryClient)
  return Object.assign(
    createValueAccessor(() => controller.current),
    {
      destroy: () => controller.destroy(),
    },
  )
}

可见 IsFetchingAccessor 并不是一个独立实现的类,而是"通用访问器工厂 + 控制器销毁句柄"的组合产物。该类型与 useIsFetching 均从包入口 packages/lit-query/src/index.ts#L43-L44 对外导出。

使用方式:调用函数或读取 current

参考文档说明:"Call the accessor or read its current property to get the number of currently fetching queries that match the filters."(调用该访问器,或读取其 current 属性,即可获取当前匹配过滤条件的正在 fetch 的查询数量。)

源码注释中的官方示例展示了在 Lit 组件中的标准用法(packages/lit-query/src/useIsFetching.ts#L131-L145):

import { LitElement, html } from 'lit'
import { useIsFetching } from '@tanstack/lit-query'

class TodosStatus extends LitElement {
  private readonly todosFetching = useIsFetching(this, {
    queryKey: ['todos'],
  })

  render() {
    return html`<span>${this.todosFetching()} active todo fetches</span>`
  }
}

三个参数依次为:

  1. host: ReactiveControllerHost —— 拥有该缓存订阅的 Lit 响应式控制器宿主(通常是 LitElement 实例),控制器会调用 host.addController(this) 挂到宿主上,随宿主的连接/断开同步订阅与清理;
  2. filters: Accessor<QueryFilters> —— 查询过滤器,默认为空对象(匹配所有查询)。QueryFilters 来自 @tanstack/query-core,常用字段包括 queryKey 等;
  3. queryClient?: QueryClient —— 可选的显式 QueryClient。省略时,控制器会从最近的已连接 QueryClientProvider 通过 Lit Context 机制解析。

filters 的类型 Accessor<T> 同样定义在 packages/lit-query/src/accessor.ts#L13,是 T | (() => T) 的二元类型。这意味着过滤条件既可以是静态对象,也可以是一个 getter 函数。当传入函数时,Lit Query 会在每次宿主更新时重新读取它,使计数跟随宿主的响应式状态变化——这一点在下一节的控制器实现中有直接体现,并被仓库测试显式覆盖(详见"测试验证"小节)。

destroy() 成员:手动销毁控制器

参考文档中 IsFetchingAccessor 唯一的成员声明是:

destroy: () => void

useIsFetching 的实现可以看到,destroy 直接委托给内部 IsFetchingControllerdestroy(),最终执行的是基类 packages/lit-query/src/controllers/BaseController.ts#L104-L121 中的逻辑:

destroy(): void {
  if (this.destroyed) {
    return
  }

  this.destroyed = true
  this.connected = false
  this.connectionAttempt += 1
  this.clearContextClient()
  // ...
  this.onDisconnected()

  if ('removeController' in this.host) {
    this.host.removeController(this)
  }
}

其语义可以概括为四点:

  • 幂等:重复调用不会重复清理(destroyed 标志位短路);
  • 断开状态:将控制器标记为已销毁并置为非连接状态,同时递增 connectionAttempt,使已排队的微任务回调因尝试序号不匹配而安全失效;
  • 清理上下文订阅:清除从 @lit/context 获取 QueryClient 的订阅(clearContextClient()),随后触发 onDisconnected(),在 IsFetchingController 中会执行 this.unsubscribe?.() 取消对 QueryCache 的订阅;
  • 脱离宿主:调用 host.removeController(this),使该控制器不再随宿主更新而被驱动。

因此,如果你手动持有访问器且希望立即释放其订阅(例如组件将被移除但尚未走完 Lit 生命周期,或你想在测试中精确控制清理时机),调用 todosFetching.destroy() 是明确的、安全的做法。

底层实现:IsFetchingController 如何得到这个数值

IsFetchingAccessor 的数值由私有类 IsFetchingControllerpackages/lit-query/src/useIsFetching.ts#L19-L114)产出,它继承 BaseController<number>,核心机制有四个:

1. 初始值为 0,按需计算。 构造函数中以 super(host, 0, queryClient) 将初始结果设为 0;若显式传入 queryClient,还会立即执行一次 computeValue() 写入结果。computeValue() 的实现非常简洁:

private computeValue(): number {
  if (!this.queryClient) {
    return 0
  }

  return this.queryClient.isFetching(readAccessor(this.filters))
}

即数值完全委托给 QueryClient.isFetching(filters),与 React/Svelte/Vue 各集成中 useIsFetching 的取值口径一致;readAccessor 负责兼容"静态对象或 getter"两种过滤器形态。

2. 订阅 QueryCache,实时刷新计数。 控制器在 onConnected() 时调用 subscribe()

private subscribe(): void {
  if (!this.queryClient) {
    return
  }

  if (this.unsubscribe) {
    return
  }

  this.unsubscribe = this.queryClient.getQueryCache().subscribe(() => {
    this.setResult(this.computeValue())
  })
}

订阅的是整个 QueryCache 的变更事件(不区分具体查询),任何查询进入/退出 fetching 状态都会触发一次重算并 setResult。由于 Lit 集成的设计是"控制器不直接触发渲染,而是请求宿主更新",BaseController.setResultpackages/lit-query/src/controllers/BaseController.ts#L136-L145)在数值变化时会通过 queueMicrotaskhost.requestUpdate() 排入一次宿主更新,保证渲染读取 accessor() 时拿到的是最新值。

3. 响应式过滤器在宿主更新时重读。 onHostUpdate() 中:

protected onHostUpdate(): void {
  if (typeof this.filters !== 'function') {
    return
  }

  this.setResult(this.syncClient() ? this.computeValue() : 0)
}

只有当 filters 是函数时才在宿主更新路径上重算——因为 getter 可能依赖响应式的宿主状态,需要在每次更新时重新读取;静态对象过滤器则不需要,避免无谓计算。

4. QueryClient 的动态解析。 省略 queryClient 参数时,BaseController 通过派发 ContextEventpackages/lit-query/src/controllers/BaseController.ts#L208-L251)向祖先 QueryClientProvider 请求 client,并维护 pre-connect / awaiting-context / bound / missing 四种解析状态;client 缺失或切换时会重新订阅(onQueryClientChanged),并在缺失时把计数回落到 0。另外,current 属性在解析状态为 missing 时会抛出 createMissingQueryClientError()packages/lit-query/src/controllers/BaseController.ts#L147-L153),这是读取访问器时可能遇到的边界行为。

测试验证

仓库测试 packages/lit-query/src/tests/counters-and-state.test.ts 中对 IsFetchingAccessor 的行为有直接覆盖:

  • 用例 S1: useIsFetching tracks filters and filter reactivity(约 L360-L420)验证了静态过滤器与响应式过滤器两条路径:对全部查询创建 isFetchingAll(传 {}),对 () => activeFilter 创建 isFetchingFiltered。两个查询并发时 isFetchingAll() === 2isFetchingFiltered() === 1;随后把 activeFilter 切到另一个 key 并触发 host.update(),计数仍保持 1(因为新过滤的 key 也在 fetch 中),直到两个查询分别 resolve 后计数归零。这正好印证了前述第 3 点:getter 形式的过滤器随宿主更新重读。
  • 同一文件中还有组合用例(约 L337-L352):useIsFetchinguseIsMutatinguseMutationState 并用,查询进入 fetch 时 isFetching() === 1,成功后回到 0,验证了计数随 QueryCache 订阅实时收敛。
  • 控制器宿主侧的生命周期(connect/disconnect/destroy 与 client 切换下的重订阅)则由 packages/lit-query/src/tests/base-controller.test.tspackages/lit-query/src/tests/client-switch-controllers.test.ts 覆盖。

小结与参考

IsFetchingAccessor = ValueAccessor<number> & { destroy: () => void },是 Lit Query 中把"QueryCache 上的计数统计"暴露给 Lit 组件的统一接口:

  • 取值:count()count.current,两者同值;
  • 过滤:filters 支持 QueryFilters 对象或返回它的 getter,getter 形态可随宿主状态响应式更新;
  • 生命周期:订阅 QueryCache,client 缺失时计数为 0destroy() 幂等地取消订阅并 removeController
  • 同源 API:useIsMutating 返回的 IsMutatingAccessoruseMutationState 返回的 MutationStateAccessor 采用同样的"ValueAccessor + destroy"形态,可对照 packages/lit-query/src/useIsMutating.tspackages/lit-query/src/useMutationState.ts 阅读。

延伸阅读可参考仓库内 Lit 集成文档 docs/framework/lit/quick-start.mddocs/framework/lit/overview.md 以及 packages/lit-query/README.md

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

项目优选

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