Svelte Query useIsFetching 详解:响应式统计正在请求中的查询数量
【免费下载链接】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
useIsFetching是 Svelte Query(@tanstack/svelte-query)提供的一个响应式工具函数,用于统计当前正在向后端发起请求(fetching)的查询数量,并返回一个可直接在 Svelte 模板与逻辑块中使用的响应式值。它非常适合实现全局加载指示器、刷新进度提示等跨查询共享 UI 状态,在 Svelte Query 参考文档 中作为核心函数 API 收录。读完本文,你将掌握它的签名、参数过滤规则、返回值语义,以及它从QueryClient到QueryCache的完整底层实现链路。
函数签名与核心能力
useIsFetching的完整类型签名如下:
function useIsFetching(filters?, queryClient?): ReactiveValue<number>;它的定义位于 packages/svelte-query/src/useIsFetching.svelte.ts#L40,并通过 packages/svelte-query/src/index.ts#L28 从@tanstack/svelte-query包对外导出:
export { useIsFetching } from './useIsFetching.svelte.js'它接收两个可选参数——filters(用于缩小统计范围的QueryFilters)与queryClient(自定义客户端实例),返回一个ReactiveValue<number>。与 React Query 的useIsFetching返回普通数字不同,Svelte 版本返回的是 Svelte 5 运行时的响应式容器对象,读取其中的.current属性即可拿到当前正在请求的查询数量,且读取行为会被 Svelte 的响应式系统自动追踪。
参数详解
filters?:QueryFilters
filters是QueryFilters<readonly unknown[]>类型,用于收窄"统计哪些查询"的范围。省略该参数时,统计所有正在请求的查询。
QueryFilters定义于 packages/query-core/src/utils.ts#L27,其字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
type | 'all' \| 'active' \| 'inactive' | 只统计 active 查询、inactive 查询或全部查询 |
exact | boolean | 是否精确匹配queryKey(默认前缀匹配) |
predicate | (query: Query) => boolean | 自定义谓词函数,逐个过滤查询 |
queryKey | TQueryKey \| TuplePrefixes<TQueryKey> | 按查询键匹配,支持前缀匹配 |
stale | boolean | 只统计过期(stale)或未过期的查询 |
fetchStatus | FetchStatus | 按请求状态过滤,FetchStatus取值为'fetching' \| 'paused' \| 'idle'(定义于 packages/query-core/src/types.ts#L665) |
最常用的写法是传入queryKey进行前缀匹配。例如useIsFetching({ queryKey: ['posts'] })会统计所有以['posts']开头的查询键对应的请求;若需精确匹配,可加exact: true。也可以通过predicate做更细粒度的判断,例如只统计某个特定数据源的请求。
queryClient?:QueryClient
queryClient用于指定自定义的QueryClient实例。省略时,useIsFetching会通过 Svelte 的 context 机制获取最近的QueryClient。这一解析逻辑位于 packages/svelte-query/src/useQueryClient.ts:
export function useQueryClient(queryClient?: QueryClient): QueryClient { if (queryClient) return queryClient return getQueryClientContext() }因此在大多数场景下,只要组件树外层通过QueryClientProvider(或setQueryClientContext)注入了客户端,直接调用useIsFetching()即可。只有需要绕过 context(例如测试环境、独立模块)时才显式传入第二个参数。
返回值 ReactiveValue<number> 的语义
返回的ReactiveValue<number>是 Svelte Query 对 Svelte 5 响应式运行时的封装。要读取当前统计值,必须访问.current:
<script lang="ts"> import { useIsFetching } from '@tanstack/svelte-query' const isFetching = useIsFetching() </script> {#if isFetching.current} <div>Queries are fetching in the background...</div> {/if}其实现位于 packages/svelte-query/src/containers.svelte.ts#L8-L21:
export class ReactiveValue<T> implements Box<T> { #fn #subscribe constructor(fn: () => T, onSubscribe: Subscriber) { this.#fn = fn this.#subscribe = createSubscriber((update) => onSubscribe(update)) } get current() { this.#subscribe() return this.#fn() } }每次访问current都会触发两件事:
- 调用
createSubscriber(来自svelte/reactivity)建立的订阅逻辑,把当前读取位置注册为queryCache的监听者; - 执行内部的
fn(),实时计算并返回当前的 fetching 数量。
当queryCache后续发生变更(查询开始/结束请求)时,订阅回调会通知 Svelte 的响应式系统,让所有读取过current的模板表达式自动重新求值——这正是useIsFetching能"响应式"跟随请求状态变化的根本原因。
使用示例
场景一:按 queryKey 前缀过滤,展示局部刷新提示
原文档给出的典型用法是只关心某个数据域内的请求。以下代码统计所有以['posts']为前缀的查询是否正在请求,并据此显示刷新提示:
<script lang="ts"> import { useIsFetching } from '@tanstack/svelte-query' // How many queries matching the posts prefix are fetching? const isFetchingPosts = useIsFetching({ queryKey: ['posts'] }) </script> {#if isFetchingPosts.current} <span>Refreshing posts...</span> {/if}场景二:全局加载指示器
不传任何参数时,useIsFetching统计的是 queryCache 中所有正在请求的查询,包括后台静默刷新、预取等不在当前屏幕上渲染的查询。这非常适合做应用级的全局进度指示:
<script lang="ts"> import { useIsFetching } from '@tanstack/svelte-query' const isFetching = useIsFetching() </script> {#if isFetching.current} <div>Queries are fetching in the background...</div> {/if}注意它与单个查询结果里的isFetching语义不同:useQuery返回的isFetching只反映该查询自身是否在请求,而useIsFetching是跨查询的聚合视角。
场景三:组合过滤条件
实际项目中可以组合多个过滤字段,精确控制统计口径。例如只统计 active(有组件正在观察)且已过期(stale)的查询:
<script lang="ts"> import { useIsFetching } from '@tanstack/svelte-query' const isFetchingActiveStale = useIsFetching({ type: 'active', stale: true, }) </script> {#if isFetchingActiveStale.current} <p>Active stale queries are refetching…</p> {/if}也可以配合enabled条件查询(参考 createQuery)观察请求的生命周期:请求发起时计数 +1,请求完成时归零。
底层实现原理
useIsFetching的完整实现非常精简,核心只有几行(packages/svelte-query/src/useIsFetching.svelte.ts#L40-L51):
export function useIsFetching( filters?: QueryFilters, queryClient?: QueryClient, ): ReactiveValue<number> { const client = useQueryClient(queryClient) const queryCache = client.getQueryCache() return new ReactiveValue( () => client.isFetching(filters), (update) => queryCache.subscribe(update), ) }整个数据流可以拆解为三个环节:
1. 获取客户端与缓存。通过useQueryClient(queryClient)拿到实际使用的QueryClient,再通过client.getQueryCache()取得底层的QueryCache。QueryCache是查询实例的存储与订阅中心。
2. 计算 fetching 数量。每次响应式求值时调用client.isFetching(filters)。该方法定义于 packages/query-core/src/queryClient.ts#L109-L114:
isFetching<TQueryFilters extends QueryFilters<any> = QueryFilters>( filters?: TQueryFilters, ): number { return this.#queryCache.findAll({ ...filters, fetchStatus: 'fetching' }) .length }可以看到,无论调用者是否传入过滤条件,isFetching都会强制附加fetchStatus: 'fetching',然后在 queryCache 中查找所有满足条件的查询并返回数量。
3. 订阅变更。构造ReactiveValue时传入(update) => queryCache.subscribe(update)。每当 queryCache 中任何查询的状态发生变化(例如请求开始、请求结束、缓存被写入),QueryCache都会触发订阅回调,从而驱动所有读取过isFetching.current的 Svelte 表达式重新执行。
一个值得注意的细节:fetching 与 paused 的区分
FetchStatus有三种取值:'fetching' | 'paused' | 'idle'(见 packages/query-core/src/types.ts#L665)。由于isFetching强制按fetchStatus: 'fetching'过滤,处于paused(例如网络离线而暂停请求)状态的查询不会被计入统计结果。这一点从源码逻辑可以直接推断:只有真正在发请求的查询才会被findAll选中。
测试验证:请求开始与结束时的计数变化
仓库中为useIsFetching提供了对应的测试组件与用例,位于 packages/svelte-query/tests/useIsFetching/useIsFetching.svelte.test.ts。测试组件 Base.svelte 中先通过setQueryClientContext(queryClient)注入客户端,再创建一个受ready开关控制的查询与一个useIsFetching():
<script lang="ts"> import { setQueryClientContext } from '../../src/context.js' import { createQuery, useIsFetching } from '../../src/index.js' setQueryClientContext(queryClient) let ready = $state(false) const query = createQuery(() => ({ queryKey: queryKey(), queryFn: () => sleep(10).then(() => 'test'), enabled: ready, })) const isFetching = useIsFetching() </script> <button onclick={() => (ready = true)}>setReady</button> <div>isFetching: {isFetching.current}</div>测试用例验证了计数的动态变化(useIsFetching.svelte.test.ts#L19-L31):
- 初始状态显示
isFetching: 0; - 点击按钮启用查询后(
enabled: true触发请求),计数变为1; - 查询完成(10ms 的
sleep结束)后,计数回到0。
这直接印证了"查询开始请求 → 计数 +1,请求结束 → 计数归零"的响应式行为,也说明useIsFetching会跨查询生命周期持续跟踪状态。
常见问题与注意事项
- 读取时一定要用
.current:useIsFetching返回的是ReactiveValue容器而非原始数字,忘记访问.current会在模板中拿到对象本身,无法正确渲染。 - 默认前缀匹配:传入
queryKey时默认按前缀匹配,['posts']会匹配['posts', 1]、['posts', 'detail']等;需要精确匹配时显式设置exact: true。 - 不区分屏幕内外:不带参数调用时,后台刷新、预取(prefetch)等不在当前组件观察范围内的请求也会被计入,这正是设计上用于全局加载指示的原因。
- 自定义 QueryClient 场景:多客户端架构下,可通过第二个参数显式指定
QueryClient,否则始终使用最近 context 中的实例。 - paused 查询不计入:网络离线等导致暂停的请求不会让计数 +1,因为底层强制按
fetchStatus: 'fetching'过滤。
小结
useIsFetching用极简的 API 抽象了"跨查询聚合请求状态"这一常见需求:参数层通过QueryFilters提供前缀匹配、精确匹配、谓词过滤等丰富的统计口径;返回值层借助ReactiveValue与queryCache.subscribe实现了完全响应式的更新链路;底层则由QueryClient.isFetching统一收敛到findAll({ fetchStatus: 'fetching' })。无论是页面级"正在刷新"提示、应用级全局加载条,还是多查询协同的业务场景,它都是 Svelte Query 生态中最直接的解决方案。
【免费下载链接】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),仅供参考