Preact Query 指南:禁用与暂停查询(Disabling / Pausing Queries)
2026/9/10 12:20:33 网站建设 项目流程

Preact Query 指南:禁用与暂停查询(Disabling / Pausing Queries)

【免费下载链接】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

在基于 Preact 的前端应用中,@tanstack/preact-queryuseQuery并不总是希望组件挂载后立刻发起请求——例如"用户点击后再加载"、"过滤条件尚未填写时不请求"、或"依赖项还不存在时等待"。本指南围绕enabled选项与skipToken两大机制,系统讲解如何禁用、延迟、按条件启动查询,并厘清isPendingisFetchingisLoading在禁用场景下的区别。读完本文,你将能正确实现"惰性查询 / 依赖查询",并在保持 TypeScript 类型安全的前提下禁用查询。

本文对应仓库文档为 Preact Query 禁用查询指南,该文档与 React Query 同主题指南 内容一一对应(前者由后者以react-query → preact-queryReact → Preact全局替换生成),文中引用的实现代码均来自当前仓库。

一、用enabled选项禁用自动执行

想要让查询"不自动运行",最直接的入口是useQuery选项中的enabled。将其置为false,查询便进入禁用状态;queryObserver.ts 中对enabled做了校验,它既可以是一个布尔值,也可以是一个返回布尔值的回调函数——当回调形式出现时,会在每次结果计算前通过resolveQueryValue取值,这意味着你可以在渲染期间基于最新 props / state 动态决定是否启用查询。

import { useQuery } from '@tanstack/preact-query' function Todos() { const { isLoading, isError, data, error, refetch, isFetching } = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList, enabled: false, }) return ( <div> <button onClick={() => refetch()}>Fetch Todos</button> {data ? ( <ul> {data.map((todo) => ( <li key={todo.id}>{todo.title}</li> ))} </ul> ) : isError ? ( <span>Error: {error.message}</span> ) : isLoading ? ( <span>Loading...</span> ) : ( <span>Not ready ...</span> )} <div>{isFetching ? 'Fetching...' : null}</div> </div> ) }

enabled = false时查询的五种行为

enabledfalse时,查询呈现以下特征:

  1. 已有缓存数据:若该queryKey此前已经取得过数据,查询会被初始化为status === 'success'(即isSuccess)状态,界面可立即渲染缓存内容;
  2. 没有缓存数据:查询从status === 'pending'fetchStatus === 'idle'开始——即"还没有数据,但也并没有在请求";
  3. 挂载时不自动请求:不会在组件挂载时触发fetch
  4. 不参与后台自动刷新:不会响应窗口聚焦(window focus)、网络重连等触发条件进行后台 refetch;
  5. 忽略失效 / 刷新指令queryClient.invalidateQueriesqueryClient.refetchQueries对它的常规调用会被忽略。

在底层实现中,这一系列约束正是由enabled贯穿query-core的多处判断完成的:query.ts 中查询的isActive()只统计enabled !== false的观察者,isStale()等失效判断同样把enabled === false的查询排除在外;queryObserver.ts 中shouldFetchOnMountshouldFetchOnshouldFetchOptionally均以resolveQueryValue(options.enabled, query) !== false作为前置门槛,从源头杜绝了"挂载自动请求"与"后台按需刷新"的触发。

与此同时,useQuery返回的refetch依旧可用:上述示例中点击按钮调用refetch()手动触发一次请求。这是禁用态下唯一常规的拉取方式。

二、永久禁用并非最佳实践:声明式优于命令式

文档特别提醒:永久禁用enabled: false永远不变)会让查询退出 TanStack Query 带来的大量特性(例如后台刷新、自动失效),同时也偏离了该库惯用的声明式用法——你实际上是在"依赖声明"与"命令式手动拉取"之间切换到了后者;而且refetch无法携带参数(你无法通过refetch(someArgs)把运行时入参传给queryFn)。

大多数情况下,你真正需要的其实是一个惰性查询(lazy query):查询先不请求,等到某个前置条件满足后再"自然地"启动——也就是把enabledfalse变为true

三、惰性查询:条件满足时自动启动

enabled的价值不在于"永久关死",而在于在稍后某个时机由 false 翻转为 true。最经典的例子是过滤器表单:只有用户输入了过滤值,才发起第一次请求。

下方代码展示的是仓库 Preact 禁用查询指南 中的示例。filter为空字符串时enabled: !!filterfalse,查询处于禁用态;一旦用户提交过滤条件,setFilter更新状态,enabled变为true,查询自动执行:

function Todos() { const [filter, setFilter] = useState('') const { data } = useQuery({ queryKey: ['todos', filter], queryFn: () => fetchTodos(filter), // ⬇️ disabled as long as the filter is empty enabled: !!filter, }) return ( <div> // 🚀 applying the filter will enable and execute the query <FiltersForm onApply={setFilter} /> {data && <TodosTable data={data} />} </div> ) }

注意queryKeyqueryFn都依赖filter——把filter放进queryKey后,每次过滤条件变化都会形成新的缓存条目,这是"依赖查询"的标准写法。更多场景可延伸阅读 依赖查询(Dependent Queries) 与 查询基础(Queries)。

isLoadingisPending/isFetching的区别

惰性查询启动时status === 'pending'自始至终成立——因为pending的语义是"还没有数据",这在禁用期间也是成立的。但它并不能用来驱动 loading 动画:禁用态下数据确实不存在(isPending为 true),可此时根本没有在请求。

正确的选择是使用派生标记isLoading(旧版称isInitialLoading)。在 queryObserver.ts 中可以看到它的确切定义:

const isFetching = newState.fetchStatus === 'fetching' const isPending = status === 'pending' const isLoading = isPending && isFetching // 只在“首次请求进行中”为 true

isLoading = isPending && isFetching:只有当查询正在第一次拉取数据时才为true。因此:

  • 禁用期(enabled: false、无数据)→isPending: trueisFetching: falseisLoading: false,适合展示"Not ready..."之类的占位;
  • 首次请求进行中 →isPending: trueisFetching: trueisLoading: true,可展示 spinner;
  • 请求完成 → 进入success,不再适用 loading 标记。

在第一节的完整示例中,正是通过isLoading区分"Loading..."与"Not ready ..."两种 UI 状态。

四、TypeScript 类型安全禁用:skipToken

如果你在使用 TypeScript,并且希望基于某个条件禁用查询,同时保持完整的类型推导,那么enabled = false有一个更优的替代方案——skipToken

import { skipToken, useQuery } from '@tanstack/preact-query' function Todos() { const [filter, setFilter] = useState<string | undefined>() const { data } = useQuery({ queryKey: ['todos', filter], // ⬇️ disabled as long as the filter is undefined or empty queryFn: filter ? () => fetchTodos(filter) : skipToken, }) return ( <div> // 🚀 applying the filter will enable and execute the query <FiltersForm onApply={setFilter} /> {data && <TodosTable data={data} />} </div> ) }

filterundefinedqueryFn传入skipToken,查询被禁用;一旦filter有值,queryFn立即替换为真正的取数函数。相比enabled,它的优势是类型层面的安全性:在filter可能为undefined的场景下,你无需编写fetchTodos(filter!)这种非空断言——类型系统会保证queryFn只在filter存在时才可能被真正调用。

实现层面:skipToken是什么?

  • skipToken定义于 query-core/src/utils.ts,本质上是一个Symbol(),并导出了对应的类型别名SkipToken
  • QueryClient的默认值归一化阶段,queryClient.ts 会做显式转换:
if (defaultedOptions.queryFn === skipToken) { defaultedOptions.enabled = false }

也就是说,skipToken在运行时会被自动映射为enabled: false,两者行为等价(唯一的差别见下文"refetch 限制")。正因如此,@tanstack/preact-query完整重导出了query-core(见 packages/preact-query/src/index.ts),你才能直接import { skipToken } from '@tanstack/preact-query',无需单独引入 query-core。

refetchskipToken的限制(重要)

重要:当queryFnskipToken时,useQuery返回的refetch()不会生效。此时调用refetch()会抛出Missing queryFn错误——因为没有可执行的查询函数。若你需要手动触发查询,请改用enabled: false,它对refetch()是放行的。除此之外,skipTokenenabled: false表现完全一致。

原因同样在源码中:当queryFn被解析时,utils.ts 中的 ensureQueryFn 会对queryFn === skipToken(或缺失)的情况返回一个"必定 reject"的函数,错误信息即Missing queryFn: '<queryHash>'。因此手动refetch拿到的是一个必然失败的查询函数。开发环境下若误调,utils.ts 还会额外在控制台打印一条配置错误提示。

skipToken的适用边界

需要留意skipToken并非任何查询 API 都能使用:

  • useQuery/useQueries/useInfiniteQuery:支持,可通过queryOptions()/infiniteQueryOptions()与它们搭配(见 queryOptions.ts 中skipToken重载的 JSDoc 示例);
  • Suspense 系列(useSuspenseQueryuseSuspenseInfiniteQueryuseSuspenseQueries不允许传入skipToken。Suspense 模式的 Hook 无法渲染"禁用态",因此 useSuspenseQuery.ts 等实现会在开发环境打印skipToken is not allowed for useSuspenseQuery之类的错误,类型层面也通过重载将其排除(参见 types.ts 相关注释);
  • Prefetch 系列(usePrefetchQueryusePrefetchInfiniteQuery:同样不允许,因为预取必然需要一个真正执行的查询函数(见 types.ts)。

五、禁用状态建模小结

目标推荐方式refetch()手动触发类型安全
永久禁用 + 手动按钮触发enabled: false✅ 可用需要自己处理可空入参
条件满足后自动启动(惰性查询)enabled: !!condition或回调需要自己处理可空入参
条件满足后自动启动 + 全程类型安全queryFn: cond ? fn : skipToken❌ 不可用(Missing queryFn)✅ 编译器保证
Suspense / Prefetch 下的禁用enabled: false,不要用skipToken遵循对应 Hook 的类型约束

辅助判断 UI 状态时,请记住:isLoading = isPending && isFetching,只有它才能表达"正在第一次加载";禁用但无数据时用isPending单独判断,展示 "Not ready" 类占位即可。

六、结语

禁用与暂停查询是 TanStack Query 数据流控制的重要一环。enabled负责"声明式地描述查询的运行依赖",适合实现过滤器、搜索框等典型的惰性查询;skipToken则在 TypeScript 场景下把同一思路做到了类型闭环。理解isPending/isFetching/isLoading的区分,以及refetchskipToken下的限制,是避免"按钮点了没反应""loading 永远转圈"等经典踩坑的关键。建议结合实际源码与 React 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询