
Preact Query 的 usePrefetchQuery在渲染阶段预取数据、消除 Suspense 请求瀑布【免费下载链接】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/queryusePrefetchQuery是tanstack/preact-query提供的一个无返回值voidHook专用于在渲染期间、Suspense 边界之前提前触发一次查询预取让下游使用useSuspenseQuery的组件无需等待即可拿到缓存数据。读完本文你将掌握它的完整签名与类型参数、options 配置约束以及它是如何在源码层面做到已有缓存即跳过、只请求一次、不产生瀑布的。本文的讲解主体是 usePrefetchQuery 参考文档同时对照其真实实现 usePrefetchQuery.tsx、类型定义 types.ts 与测试用例 usePrefetchQuery.test.tsx 做纵深展开。适用背景Suspense 渲染模式下为何需要渲染期预取在 Preact 生态中使用 TanStack Query 时useSuspenseQuery会让组件在数据未就绪时挂起suspend由外层Suspense展示 fallback。如果数据请求只能等到组件挂起后才发起那么嵌套多层的 Suspense 组件就会形成串行的请求瀑布request waterfall每一层都要等上一层的数据返回并完成渲染后才能开始发起自己的请求。usePrefetchQuery正是为了打破这种串行等待而生它把发起请求的动作提前到渲染函数体内执行。由于它不读取数据、不返回任何内容也不会导致挂起因此可以在 Suspense 边界之上、甚至在整棵子树渲染之前就让多个查询并行进入 in-flight 状态。关于该问题背景可进一步阅读 请求瀑布指南。从包的导出列表可以确认它是一等公民 API在 index.ts 中与useSuspenseQuery、usePrefetchInfiniteQuery等一起被导出。函数签名入参、返回与类型参数参考文档给出如下签名function usePrefetchQueryTQueryFnData, TError, TData, TQueryData, TQueryKey(options, queryClient?): void;完整定义位于 usePrefetchQuery.tsx:42。需要注意的核心定位有三点usePrefetchQuery不返回任何内容返回类型是void。它只负责在渲染期间发射一次预取请求其结果写入查询缓存由后续消费数据的 Hook 读取。典型调用位置是在一个包裹着使用useSuspenseQuery组件的 Suspense 边界之前/之上。此时预取已经发起等下层组件真正挂起并读取缓存时数据往往已经就绪。你可以向它传递一切能传给queryClient.query的选项但其中queryKey始终必填而queryFn在未定义默认查询函数default query function时也是必填的。五个泛型参数与默认值泛型参数默认值含义TQueryFnDataunknownqueryFn解析出的数据类型TErrorErrorqueryFn可能抛出的错误类型源码中通过 query-core 的DefaultError别名表达见 usePrefetchQuery.tsx:44TDataTQueryFnData经select转换后的data类型未使用select时即TQueryFnDataTQueryDataTQueryFnData实际存入查询缓存的数据类型即select与placeholderData的输入通常与TQueryFnData相同TQueryKeyreadonly unknown[]即QueryKeyqueryKey的类型由于usePrefetchQuery从不把data读出来使用所以这些泛型更多是为了让传入的 options 对象保持类型安全并允许同一份 options 在别处被复用。参数说明options 与可选的 queryClientoptionsUsePrefetchQueryOptionsoptions的类型是 UsePrefetchQueryOptions其底层结构定义于 types.ts:79export type UsePrefetchQueryOptions... DistributiveOmit QueryExecuteOptionsTQueryFnData, TError, TData, TQueryData, TQueryKey, queryFn { queryFn?: Exclude..., SkipToken }翻译成直觉上的语义就是queryFn之外的任何queryClient.query选项如queryKey、staleTime、gcTime、retry、networkMode等都可以原样传入。两个类型层面的约束值得注意queryKey始终是必填的——参考文档中明确说明即便其他选项都可选预取也必须知道把结果写入哪条查询。queryFn在类型上是可选的但实际总是需要真的能跑起来其类型被Exclude..., SkipToken处理意味着skipToken不允许作为这里的值——预取必须有实际的查询函数可执行除非已经通过queryClient配置了默认查询函数可参考 默认查询函数指南。若既无queryFn又无默认查询函数调用将无法真正发起请求。queryClient自定义实例第二个参数queryClient是可选的QueryClient。当你不传它时usePrefetchQuery会像其他所有 query Hook 一样从最近的 Context 中读取QueryClient当你需要绕过 Context、显式指定某个实例时直接传入即可。在源码实现中这一解析逻辑发生在 usePrefetchQuery.tsx:58 的const client useQueryClient(queryClient)。其语义由 QueryClientProvider.tsx:21 的useQueryClient决定优先返回显式传入的实例否则取QueryClientContext中的值如果两者都没有会抛出错误No QueryClient set, use QueryClientProvider to set one。因此应用根部通常需要用 QueryClientProvider 注入 client它也负责在挂载/卸载时调用client.mount()/client.unmount()。源码透视预取为什么廉价且不会重复请求usePrefetchQuery的全部实现只有几行却集中体现了其全部语义。核心代码在 usePrefetchQuery.tsx:58-62const client useQueryClient(queryClient) if (!client.getQueryState(options.queryKey)) { void client.query(options).catch(noop) }可以拆解出三个关键机制幂等跳过if (!client.getQueryState(options.queryKey))只有在这条 query 当前不存在任何缓存状态时预取才会真正执行。文档明确强调只要缓存里已有状态——包括上一次尝试遗留的pending或error状态——预取就会被跳过。因此把usePrefetchQuery放在一个会在每次渲染都执行的组件里是廉价且安全的它不会重新拉取已经在缓存中、或已经在途in-flight的数据。直接调用底层client.query(options)传入的 options 被原样交给queryClient.query执行这正是你能传给queryClient.query的一切都能传给它的实现来源。请求失败时错误会被写入该 query 的缓存状态而不是在 Hook 内部被吞掉或抛出——失败的错误信息由后续真正挂起读取的useSuspenseQuery组件负责呈现见下文测试用例 3。void ... .catch(noop)吸收 promise由于 Hook 不向调用方暴露任何结果执行client.query(options)得到的 promise 无人消费catch(noop)用于避免在并发执行期间出现未处理的 promise rejection。noop是从tanstack/query-core导入的空操作函数见 usePrefetchQuery.tsx:1。标准用法在 Suspense 边界之前触发预取参考文档给出的示例完整地展示了推荐用法。预取被放在渲染函数体顶部早于包裹Posts的Suspenseimport { Suspense } from preact/compat import { usePrefetchQuery } from tanstack/preact-query function App() { // 在渲染期间触发预取早于下面的 suspense 边界。 usePrefetchQuery({ queryKey: [posts], queryFn: fetchPosts, }) return ( Suspense fallback{h1Loading posts.../h1} Posts / /Suspense ) }其效果是App一渲染[posts]查询就已开始在后台拉取Posts组件内部通过useSuspenseQuery参考 useSuspenseQuery 文档读取同一queryKey时要么直接命中已就绪的缓存瞬间完成渲染要么至多只展示一次 fallback等待时间与先发请求再渲染路径一致但无需串行。若同时预取多条查询它们会并行发起从根本上消除嵌套 Suspense 结构下的请求瀑布。行为契约由测试用例验证的五个边界仓库中的行为测试 usePrefetchQuery.test.tsx 把上述语义固化成了一套可验证的契约分别对应如下场景查询状态不存在时发起预取第 33 行页面先展示Loading...fallback计时推进后渲染出data: prefetchQuery且queryFn只被调用一次——说明usePrefetchQuery与useSuspenseQuery共享同一条缓存后挂起的消费者不会重复请求。查询状态已存在时跳过预取第 68 行预先通过queryClient.query填充缓存后再挂载包含usePrefetchQuery的组件树queryFn不会被再次调用页面直接渲染出既有数据。这印证了每次渲染都调用它也很廉价的设计承诺。错误会向下传递且不重取失败查询第 103 行当查询曾经失败、缓存中残留error状态时usePrefetchQuery会跳过该查询错误最终由ErrorBoundary呈现为Oops!而不是由预取静默重试或再次发起请求。在 Suspense 边界内部使用也不会死循环第 148 行即使把usePrefetchQuery放进与挂起组件共享的 Suspense 子树内由于状态一旦存在即跳过渲染循环不会反复触发新的请求queryFn同样只执行一次。错误可被重置后重新拉取第 182 行当预取路径上遗留了error状态导致组件始终挂起在错误上时通过useQueryErrorResetBoundary见 参考文档的reset配合ErrorBoundary的onReset清空边界后预取可以重新生效并成功拉取——验证了错误恢复路径。此外还有一个专门针对瀑布场景的测试第 242 行App中连续调用三次usePrefetchQuery预取嵌套组件各自的数据测试断言渲染开始后三条查询的fetchStatus全部立即为fetching并行在途整个过程中 Suspense 的Fallback只渲染一次最终三个嵌套组件几乎同时展示数据——这是预取能消灭瀑布的直接证据。相关 API 与延伸阅读usePrefetchQuery并不是孤立存在的它与下面这些 API 共同构成 Suspense 预取体系无限查询的对应物usePrefetchInfiniteQuery 参考文档 提供同样的渲染期预取能力用于useSuspenseInfiniteQuery其选项类型为 types.ts:117 处的UsePrefetchInfiniteQueryOptions。正式指南Prefetching 指南 系统介绍预取在该框架中的整体策略请求瀑布指南 则专门讨论它要解决的问题形态。消费端 HookuseSuspenseQuery、useSuspenseQueries非 Suspense 场景下的等效替代是经典的 useQuery。相关选项类型UseSuspenseQueryOptions 与 UseBaseQueryOptions 可帮助你理解queryClient.query选项的完整集合。小结总结下来usePrefetchQuery是一个零返回值、渲染期副作用式的 API在 Suspense 边界之上尽早并行发起请求让下游useSuspenseQuery组件直接命中热缓存内部通过getQueryState判断实现有状态即跳过的幂等语义配以catch(noop)吸收未消费的 promise。其实际行为已被仓库内的 6 组测试完整固化从只请求一次失败不重试错误可恢复到消除三层嵌套瀑布都有据可查可以直接作为你在项目中编排预取逻辑时的行为参照。【免费下载链接】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),仅供参考