@tanstack/preact-query 的 QueryClientProvider:让 Preact 应用全局共享 QueryClient 的根组件
2026/9/9 15:14:32 网站建设 项目流程

@tanstack/preact-query 的 QueryClientProvider:让 Preact 应用全局共享 QueryClient 的根组件

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

QueryClientProvider 是@tanstack/preact-query中把QueryClient实例注入 Preact 组件树的标准入口,它在组件挂载与卸载时分别调用client.mount()/client.unmount(),从而把客户端订阅到 focus / online 事件。读完本文,你将掌握 QueryClientProvider 的完整使用姿势、props 语义、基于 Context 的传递机制,以及它与useQueryClientuseQuery等 Hook 协同工作时的注意事项。

一、QueryClientProvider 是什么

@tanstack/preact-query的架构中,QueryClient负责管理查询缓存(QueryCache)、变更缓存(MutationCache)与各类默认选项,是整个数据获取体系的中枢对象。为了让组件树中任意层级的useQueryuseMutationuseQueryClient等 Hook 都能访问到同一个客户端,需要把QueryClient放到 Preact 的 Context 中——这正是QueryClientProvider的职责。

官方参考文档 QueryClientProvider.md 给出了其函数签名:

function QueryClientProvider(__namedParameters): VNode;

组件接收一个名为client的必填属性,把传入的children包裹在上下文 Provider 中返回,使子树内的所有 Hook 都能通过useQueryClient读取到这个QueryClient实例。

该组件的类型定义与实现位于 QueryClientProvider.tsx,与 React 版 react-query/src/QueryClientProvider.tsx、Solid 版 solid-query/src/QueryClientProvider.tsx 保持同构的设计——它属于框架适配层,真正的心智模型是"全局唯一客户端 + 上下文注入"。

二、props 语义:client 与 children

QueryClientProvider接受的属性由类型别名QueryClientProviderProps定义,其完整声明见 QueryClientProviderProps.md 与源码 QueryClientProvider.tsx:

export type QueryClientProviderProps = { /** * **Required** * * The `QueryClient` instance to provide. */ client: QueryClient /** * The components that get access to the provided `QueryClient`. */ children?: ComponentChildren }

两个属性含义如下:

属性类型必填说明
clientQueryClient要向下注入的QueryClient实例,通常由new QueryClient()创建
childrenComponentChildren(Preact 组件子节点类型)可以读取该QueryClient的组件子树

注意children类型来自 Preact 自身的ComponentChildren,意味着它可以是单个 VNode、组件数组、文本节点等 Preact 合法子内容。

三、最小可用示例

参考文档中给出的最简用法如下,这也是绝大多数应用的标准起步写法:

import { QueryClient, QueryClientProvider } from '@tanstack/preact-query' const queryClient = new QueryClient() function App() { return <QueryClientProvider client={queryClient}>...</QueryClientProvider> }

queryClient通常被创建在组件外部(模块级作用域),从而保证整个应用生命周期内复用同一个实例,避免每次渲染重新创建导致缓存丢失。

仓库自带的真实可运行示例 examples/preact/simple/src/index.tsx 展示了完整用法:创建全局queryClient,用QueryClientProvider包裹<App />,子组件内部通过useQuery请求 GitHub 仓库信息:

import { render } from 'preact' import { QueryClient, QueryClientProvider, useQuery, } from '@tanstack/preact-query' const queryClient = new QueryClient() export function App() { return ( <QueryClientProvider client={queryClient}> <Example /> </QueryClientProvider> ) } const Example = () => { const { isPending, error, data, isFetching } = useQuery({ queryKey: ['repoData'], queryFn: async () => { const response = await fetch( 'https://api.github.com/repos/TanStack/query', ) return await response.json() }, }) if (isPending) return 'Loading...' if (error !== null) return 'An error has occurred: ' + error.message return ( <div> <h1>{data.full_name}</h1> <p>{data.description}</p> <strong>👀 {data.subscribers_count}</strong>{' '} <strong>✨ {data.stargazers_count}</strong>{' '} <strong>🍴 {data.forks_count}</strong> <div>{isFetching ? 'Updating...' : ''}</div> </div> ) }

该示例同时演示了三个要点:Provider 位于组件树根部、业务组件无需手动传递客户端、子组件直接消费全局 client 发起的查询状态。想了解组件树应如何组织,可结合 preact 快速上手文档 阅读。

四、源码视角:Provider 内部做了什么

只看 API 文档容易把QueryClientProvider当作一个"无脑透传"的包裹组件。但阅读其实现 QueryClientProvider.tsx 会发现,它在挂载阶段承担了两个关键职责:

export const QueryClientProvider = ({ client, children, }: QueryClientProviderProps): VNode => { useEffect(() => { client.mount() return () => { client.unmount() } }, [client]) return ( <QueryClientContext.Provider value={client}> {children} </QueryClientContext.Provider> ) }

4.1 生命周期绑定:mount / unmount

组件挂载后,通过useEffect立即调用client.mount(),并利用 Preact Hook 的清理函数在卸载时调用client.unmount()。effect 的依赖数组是[client],也就是说当传入的client实例发生变化时,旧客户端会被正确卸载、新客户端会被挂载——这为运行时"热切换"客户端提供了保证。

官方参考文档 QueryClientProvider.md 对这套行为给出的说明是:当应用重新获得焦点或恢复在线时,客户端会恢复所有被暂停(paused)的 mutation,并按需重新拉取数据

4.2 mount / unmount 的底层实现

QueryClient.mount()/QueryClient.unmount()定义在 query-core 的 queryClient.ts:

mount(): void { this.#mountCount++ if (this.#mountCount !== 1) return this.#unsubscribeFocus = focusManager.subscribe(async (focused) => { if (focused) { await this.resumePausedMutations() this.#queryCache.onFocus() } }) this.#unsubscribeOnline = onlineManager.subscribe(async (online) => { if (online) { await this.resumePausedMutations() this.#queryCache.onOnline() } }) } unmount(): void { this.#mountCount-- if (this.#mountCount !== 0) return this.#unsubscribeFocus?.() this.#unsubscribeFocus = undefined this.#unsubscribeOnline?.() this.#unsubscribeOnline = undefined }

从源码可以提炼出三个值得注意的实现细节:

  1. 订阅 focus 与 online 事件mount()会向focusManageronlineManager订阅事件。窗口重新聚焦时执行"恢复暂停的 mutation +queryCache.onFocus()";网络重新在线时执行"恢复暂停的 mutation +queryCache.onOnline()",这正是 Provider 能让查询在"切回标签页 / 恢复网络"后自动刷新的根本原因。
  2. 引用计数防抖#mountCount字段保证同一个 client 被多个 Provider 同时挂载时,底层事件订阅只建立一次;全部卸载后才真正取消订阅。这意味着你可以安全地在一个应用中嵌套多个QueryClientProvider,而不会重复注册监听器。
  3. 幂等安全:重复调用mount()(在已挂载状态下再次调用)不会重复订阅,重复unmount()也不会在下溢时出错,边界处理集中在 count 判断上。

这些细节解释了为什么文档强调"由 Provider 负责调用client.mount()/client.unmount()"——开发者不需要(也不应该)手动调用这两个方法。

五、通过 useQueryClient 消费上下文

Provider 注入的客户端通过一个模块级导出的 Context 来传递,其定义同样位于 QueryClientProvider.tsx:

export const QueryClientContext = createContext<QueryClient | undefined>( undefined, )

配套的 Hook 是 useQueryClient.md 中描述的useQueryClient,实现见 QueryClientProvider.tsx:

export const useQueryClient = (queryClient?: QueryClient) => { const client = useContext(QueryClientContext) if (queryClient) { return queryClient } if (!client) { throw new Error('No QueryClient set, use QueryClientProvider to set one') } return client }

useQueryClient的取值优先级为:

  1. 显式传入的可选参数queryClient(用于强制使用某个自定义客户端);
  2. 否则返回 Context 中"最近一层"的QueryClient
  3. 如果组件树中根本没有QueryClientProvider,会抛出错误No QueryClient set, use QueryClientProvider to set one

这一行为意味着:useQueryuseMutation等 Hook 一旦脱离 Provider 使用,会直接报错。在实际项目中,这通常发生在把查询 Hook 用到了被渲染在 Provider 外部的组件、Portal、独立挂载的弹层里——排查时优先检查组件树位置即可。

由该源码可推断一个重要能力:由于 Preact Context 支持嵌套覆盖,你完全可以在应用中用多个QueryClientProvider形成"部分隔离"的区域,让特定子树使用不同的缓存与配置(例如管理后台使用独立 client)。useQueryClient的可选参数则为"绕过 Context、显式指定客户端"提供了逃生通道,这在编写组件库、测试夹具时尤其有用。

六、与其他数据源 Provider 的关系

除了QueryClientProvider@tanstack/preact-query还暴露了管理"恢复状态"的上下文组件,例如 IsRestoringProvider 与配套的useIsRestoringHook,以及用于 SSR / 持久化场景的 HydrationBoundary。它们的共性是:通过 Preact Context 把基础设施对象(QueryClient、恢复标记、水合数据)注入组件树,再由上层查询 Hook 消费。

在项目中的典型组装顺序通常为:

<QueryClientProvider client={queryClient}> <IsRestoringProvider value={isRestoring}> {/* 应用的其余组件树 */} </IsRestoringProvider> </QueryClientProvider>

关于其余全部公开函数与变量的完整索引,可查阅 preact 参考索引。

七、最佳实践小结

结合官方文档、源码与示例,在实际项目中推荐遵循以下约定:

  1. 全局单例const queryClient = new QueryClient()写在模块顶部,随应用进程复用,避免组件内反复new
  2. 挂在最根部:把QueryClientProvider放在 App 根节点附近,保证所有需要查询能力的子树都能取到客户端。
  3. 不要手动 mount/unmount:生命周期由 Provider 的useEffect托管,手写调用只会造成重复订阅或内存泄漏。
  4. 缺失 Provider 的错误处理:牢记useQueryClient在无 Provider 时会 throw,用错误栈定位"脱离 Provider 的查询 Hook"。
  5. 多实例场景利用嵌套:需要隔离缓存时嵌套多层 Provider,底层 count 机制保证不会重复订阅全局事件。
  6. 想深入了解QueryClient的完整 API(缓存管理、默认选项、resumePausedMutations等),可直接阅读 query-core 的 QueryClient 类源码,它是 Provider 所注入对象的全部能力来源。

【免费下载链接】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),仅供参考

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

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

立即咨询