资讯详情

Apollo Client 查询指南:useQuery 与 useLazyQuery 完整实战手册

📅 2026/9/21 0:24:41 | 华诺云谱 👁 阅读
Apollo Client 查询指南:useQuery 与 useLazyQuery 完整实战手册
前端GraphQL【免费下载链接】apollo-clientThe industry-leading GraphQL client for TypeScript, JavaScript, React, Vue, Angular, and more. Apollo Client delivers powerful caching, intuitive APIs, and comprehensive developer tools to accelerate your app development.项目地址https://gitcode.com/gh_mirrors/ap/apollo-client点击查看免费下载本文基于 Apollo Client 仓库中的 Queries 参考文档系统讲解在 React 应用中通过useQuery执行查询、管理变量、处理加载与错误状态、轮询与重新获取、缓存策略Fetch Policies以及条件查询的完整方案。你将掌握从基础用法到源码级原理的查询开发能力并能根据场景在useQuery、useLazyQuery与client.query之间做出正确选型。在大部分应用中每个页面应只使用一次查询 Hook其余页面级组件应配合组件内聚 fragment 数据掩码data masking使用 fragment 读取 HookuseFragment、useSuspenseFragment这是避免过度请求与重复订阅的关键实践。目录useQuery Hook查询变量Query Variables加载与错误状态useLazyQuery轮询与重新获取Polling and RefetchingFetch Policies 缓存策略条件查询Conditional QueriesuseQuery HookuseQuery是 Apollo Client 在非 Suspense 应用中获取数据的主要方式它返回必须由开发者处理的loading与error状态。在 Suspense 应用中请改用useSuspenseQuery或useBackgroundQuery详见仓库文档 Suspense Hooks 参考。基本用法import { gql } from apollo/client; import { useQuery } from apollo/client/react; const GET_DOGS gql query GetDogs { dogs { id breed displayImage } } ; function Dogs() { const { loading, error, data } useQuery(GET_DOGS); if (loading) return pLoading.../p; if (error) return pError: {error.message}/p; return ul{data?.dogs.map((dog) li key{dog.id}{dog.breed}/li)}/ul; }注意useQuery应从apollo/client/react导入gql从apollo/client导入。当组件渲染时useQuery返回包含loading、error、dataState、data等属性的对象供你驱动 UI 渲染。返回对象Return Objectconst { data, // Query result data loading, // True during initial load error, // ApolloError if request failed networkStatus, // Detailed network state (1-8) dataState, // For TypeScript type narrowing (AC 4.x) refetch, // Function to re-execute query fetchMore, // Function for pagination startPolling, // Start polling at interval stopPolling, // Stop polling subscribeToMore, // Add subscription to query updateQuery, // Manually update query result client, // Apollo Client instance called, // True if query has been executed previousData, // Previous data (useful during loading) } useQuery(QUERY);从源码层面看这个返回对象并非凭空生成useQuery内部通过client.watchQuery(watchQueryOptions)创建ObservableQuery实例并通过useSyncExternalStore订阅其 RxJS 流使用asapScheduler延迟调度避免渲染中途更新引发的同步发射问题返回的refetch、fetchMore、updateQuery、startPolling、stopPolling、subscribeToMore等方法都是对observable上同名方法的绑定见 useQuery.ts。此外返回对象中还包含observable与variables字段previousData由内部resultData.previousData提供即便查询或 client 发生变化也会保持连续性。networkStatus对应仓库 networkStatus.ts 中的NetworkStatus枚举其取值如下注意枚举中没有数值 5| 值 | 名称 | 含义 | | -- | ---- | ---- | | 1 |loading| 查询从未运行过且当前正在运行即使缓存返回了部分数据、查询仍然发出也是此状态 | | 2 |setVariables| 调用了setVariables并因此发出查询等待结果返回 | | 3 |fetchMore| 调用了fetchMore查询正在飞行中 | | 4 |refetch| 调用了refetch重新获取请求正在飞行中 | | 6 |poll| 轮询查询正在飞行中例如每 10 秒轮询一次在请求发出但未返回时切为此状态 | | 7 |ready| 查询无请求在飞行、无错误发生一切正常 | | 8 |error| 无请求在飞行但检测到一个或多个错误 | | 9 |streaming|defer查询已收到第一块结果但完整结果尚未全部流式传输到客户端 |dataState是 Apollo Client 4.x 引入的 TypeScript 类型收窄字段取值如empty、complete、partial、streaming配合TypedDocumentNode可以让data的类型随查询状态精确推导。查询变量Query Variables基础变量在 GraphQL 查询中通过$变量名: 类型!声明变量并在useQuery的variables选项中传入const GET_DOG gql query GetDog($breed: String!) { dog(breed: $breed) { id displayImage } } ; function DogPhoto({ breed }: { breed: string }) { const { loading, error, data } useQuery(GET_DOG, { variables: { breed }, }); if (loading) return null; if (error) return pError: {error.message}/p; return img src{data.dog.displayImage} alt{breed} /; }TypeScript 类型为了获得更好的类型安全建议使用TypedDocumentNode而非手动传入泛型参数import { gql, TypedDocumentNode } from apollo/client; import { useQuery } from apollo/client/react; interface GetDogData { dog: { id: string; displayImage: string; }; } interface GetDogVariables { breed: string; } const GET_DOG: TypedDocumentNodeGetDogData, GetDogVariables gql query GetDog($breed: String!) { dog(breed: $breed) { id displayImage } } ; const { data } useQuery(GET_DOG, { variables: { breed: bulldog }, }); // data?.dog is fully typed在仓库的 useQuery.ts 中手动指定useQueryTData, TVariables泛型的签名已被标记为deprecated官方推荐依赖 TypeScript 的类型推断配合正确类型的TypedDocumentNode来获得准确的查询结果类型。仓库还在 integration-tests/type-tests/signatures 下维护了针对query、mutate、createQueryPreloader等签名的类型测试用例以验证这些重载行为。动态变量当variables变化时查询会自动重新执行function DogSelector() { const [breed, setBreed] useState(bulldog); // Query automatically re-runs when breed changes const { data } useQuery(GET_DOG, { variables: { breed }, }); return ( select value{breed} onChange{(e) setBreed(e.target.value)} option valuebulldogBulldog/option option valuepoodlePoodle/option /select ); }从实现上看useQuery通过useResubscribeIfNecessary对比前后watchQueryOptions当变量发生变化!equal(previousOptions.variables, options.variables)或 fetch policy 在standby与其它策略之间切换时会调用observable.reobserve(watchQueryOptions)触发重新获取见 useQuery.ts。加载与错误状态使用 previousData在变量切换加载新数据时通过previousData保留上一次的结果避免 UI 空白闪烁function UserProfile({ userId }: { userId: string }) { const { loading, data, previousData } useQuery(GET_USER, { variables: { id: userId }, }); // Show previous data while loading new data const displayData data ?? previousData; return ( div {loading LoadingSpinner /} {displayData UserCard user{displayData.user} /} /div ); }源码中previousData的维护逻辑位于订阅回调内当新结果的数据与旧数据不相等时旧数据会被保存为resultData.previousData从而保证loading期间组件仍可读取上一次的数据见 useQuery.ts。网络状态networkStatus配合notifyOnNetworkStatusChange: trueloading不仅会在首次加载时为true在refetch、fetchMore、轮询等后续网络请求进行期间也会变为true此时networkStatus可用于区分具体请求类型import { NetworkStatus } from apollo/client; function Dogs() { const { loading, error, data, networkStatus, refetch } useQuery(GET_DOGS, { notifyOnNetworkStatusChange: true, }); if (networkStatus NetworkStatus.refetch) { return pRefetching.../p; } if (loading) return pLoading.../p; if (error) return pError: {error.message}/p; return ( button onClick{() refetch()}Refresh/button ul {data.dogs.map((dog) ( li key{dog.id}{dog.breed}/li ))} /ul / ); }useLazyQuery当需要在用户触发事件如点击按钮时执行查询而不是在组件挂载时立即执行应使用useLazyQuery。重要提示useLazyQuery并不保证发起网络请求——它只是设置变量。如果数据已在缓存中这并不构成一次 refetch。因此只有当你需要消费返回元组的第二个值loading、data、error状态来将缓存数据与组件同步时才应使用useLazyQuery如果只需要 promise 结果请直接使用client.query。基本用法useLazyQuery返回一个元组[execute, result]import { gql } from apollo/client; import { useLazyQuery } from apollo/client/react; const GET_DOG_PHOTO gql query GetDogPhoto($breed: String!) { dog(breed: $breed) { id displayImage } } ; function DelayedQuery() { const [getDog, { loading, error, data, called }] useLazyQuery(GET_DOG_PHOTO); if (called loading) return pLoading.../p; if (error) return pError: {error.message}/p; return ( div {data?.dog img src{data.dog.displayImage} /} button onClick{() getDog({ variables: { breed: bulldog } })} Get Bulldog Photo /button /div ); }注意called字段在首次调用execute之前它为false此时data为undefined、dataState为empty调用后才变为true见 useLazyQuery.ts 中的类型定义。从实现上看useLazyQuery在挂载时以fetchPolicy: standby创建ObservableQuery不发起请求当execute被调用时再通过observable.reobserve({ fetchPolicy, variables, context })真正执行查询并将 fetch policy 从standby切回initialFetchPolicy见 useLazyQuery.ts。此外execute被禁止在渲染期间调用——源码中会通过useRenderGuard抛出 invariant 错误useLazyQuery: execute should not be called during render. To start a query during render, use the useQuery hook.useLazyQuery返回结果中的refetch、fetchMore、updateQuery、startPolling、stopPolling、subscribeToMore属于急切方法EAGER_METHODS调用它们会执行查询而无需先调用execute但如果从未执行过查询调用这些方法会触发 invariant 错误见 useLazyQuery.ts。何时改用 client.query如果只需要 promise 结果而不消费 Hook 返回的loading/error/data状态请改用client.queryimport { useApolloClient } from apollo/client/react; function SearchDogs() { const client useApolloClient(); const [search, setSearch] useState(); const handleSearch async () { try { const { data } await client.query({ query: SEARCH_DOGS, variables: { query: search }, }); console.log(Found dogs:, data.searchDogs); } catch (error) { console.error(Search failed:, error); } }; return ( div input value{search} onChange{(e) setSearch(e.target.value)} / button onClick{handleSearch}Search/button /div ); }轮询与重新获取Polling and Refetching轮询Polling通过pollInterval让查询以固定间隔自动重新执行function LiveFeed() { const { data, startPolling, stopPolling } useQuery(GET_FEED, { pollInterval: 5000, // Poll every 5 seconds }); // Or control polling dynamically useEffect(() { startPolling(5000); return () stopPolling(); }, [startPolling, stopPolling]); return Feed items{data?.feed} /; }源码层面startPolling(pollInterval)会设置this.options.pollInterval pollInterval并调用updatePolling()开始定时任务stopPolling()则将pollInterval置为0并停止见 ObservableQuery.ts。轮询请求进行期间networkStatus会切换为NetworkStatus.poll值为 6。此外useQuery选项还支持skipPollAttempt一个返回布尔值的函数用于在特定条件下跳过某次轮询。手动重新获取Manual Refetchingrefetch可无参调用也可传入新变量function DogList() { const { data, refetch } useQuery(GET_DOGS); return ( div button onClick{() refetch()}Refresh/button button onClick{() refetch({ breed: poodle })} Refetch Poodles /button ul{data?.dogs.map((dog) li key{dog.id}{dog.breed}/li)}/ul /div ); }从 ObservableQuery.ts 的实现看refetch有以下关键行为除非当前 fetch policy 是no-cache保持no-cache否则一律以network-only覆盖当前策略强制走网络确保重新获取真实发生会临时关闭轮询pollInterval: 0传入新变量时通过getVariablesWithDefaults合并缺省变量并触发NetworkStatus.refetch值为 4返回的 promise 带有.retain()方法调用后即使ObservableQuery不再被组件订阅网络操作也会继续执行refetch()保证 observable 一定会发射一个值即使结果与上一次深度相等。Fetch Policies 缓存策略Fetch Policy 控制查询与缓存之间的交互方式。以下策略类型定义可直接在仓库 watchQueryOptions.ts 中查看。Policy描述cache-first有缓存则返回缓存数据否则才发起网络请求默认值cache-only只返回缓存数据绝不发起网络请求cache-and-network立即返回缓存数据同时发起网络请求并用新数据更新缓存network-only总是发起网络请求、更新缓存忽略缓存数据no-cache总是发起网络请求既不读取也不写入缓存standby与cache-first相同但不会自动更新不主动订阅网络结果其中cache-first、network-only、cache-only、no-cache是核心FetchPolicycache-and-network与standby仅适用于可观测查询WatchQueryFetchPolicy。standby专用于未被主动观察、但需要供refetch与updateQueries使用的查询——useLazyQuery与skipToken正是基于它实现不发起请求的。使用示例// Real-time data - always fetch const { data } useQuery(GET_NOTIFICATIONS, { fetchPolicy: network-only, }); // Static data - prefer cache const { data } useQuery(GET_CATEGORIES, { fetchPolicy: cache-first, }); // Show cached data while fetching fresh data const { data, loading } useQuery(GET_POSTS, { fetchPolicy: cache-and-network, }); // Fetch once, then use cache const { data } useQuery(GET_USER_PROFILE, { fetchPolicy: network-only, nextFetchPolicy: cache-first, });nextFetchPolicynextFetchPolicy用于指定首次请求之后后续请求使用的策略实现首次强制刷新、后续走缓存的分阶段行为// First request: network-only // Subsequent requests: cache-first const { data } useQuery(GET_POSTS, { fetchPolicy: network-only, nextFetchPolicy: cache-first, }); // Or use a function for more control const { data } useQuery(GET_POSTS, { fetchPolicy: network-only, nextFetchPolicy: (currentFetchPolicy, { reason, observable }) { if (reason after-fetch) { return cache-first; } return currentFetchPolicy; }, });函数形式接收(currentFetchPolicy, context)两个参数context中reason的取值为after-fetch首次网络请求完成后或variables-changed变量变化后。源码中 applyNextFetchPolicy 的实现逻辑为当fetchPolicy standby时不做任何改动当nextFetchPolicy是函数时直接以返回值覆盖当前策略当reason variables-changed时重置为initialFetchPolicy确保变量变化后走初始策略而非被降级后的策略其余情况将fetchPolicy设为nextFetchPolicy的字符串值。这也解释了为何network-only/cache-and-network这类每次都会触发网络请求的策略通常要搭配nextFetchPolicy使用——否则缓存更新会反复触发不必要的网络请求。条件查询Conditional Queries使用 skipToken推荐skipToken是一个特殊的哨兵值实现为Symbol.for(apollo.skipToken)见 constants.ts用于在条件不满足时跳过查询同时不会引发 TypeScript 关于必填变量的类型问题import { skipToken } from apollo/client; function UserProfile({ userId }: { userId: string | null }) { const { data } useQuery( GET_USER, !userId ? skipToken : ( { variables: { id: userId }, } ) ); return userId ? Profile user{data?.user} / : pSelect a user/p; }从实现看当传入skipToken时useQuery会生成fetchPolicy: standby的 watch options 并打上variablesUnknownSymbol标记使查询进入不主动请求的待命状态当后续渲染中传入真实 options 时再通过useResubscribeIfNecessary恢复initialFetchPolicy并开始执行见 useQuery.ts。Skip Option备选方案function UserProfile({ userId }: { userId: string | null }) { const { data } useQuery(GET_USER, { variables: { id: userId! }, skip: !userId, // Dont execute if no userId }); return userId ? Profile user{data?.user} / : pSelect a user/p; }注意skipToken优先于skip因为skip方案中userId可能为null通常需要使用非空断言操作符!容易引发类型问题。源码中skip: true的实现同样是把fetchPolicy置为standby同时将原策略保存为initialFetchPolicy见 useQuery.ts。SSR 跳过在服务端渲染SSR期间跳过查询// Skip during server-side rendering const { data } useQuery(GET_USER_LOCATION, { skip: typeof window undefined, ssr: false, });ssr: false配合skip可以避免查询在服务端执行从而把请求推迟到客户端。对于更完整的服务端渲染场景仓库还提供了 getDataFromTree 与renderToStringWithData等 SSR 工具可查阅 服务端渲染文档 进一步了解。小结本文围绕 Apollo Client 的查询能力从useQuery基础用法、变量与类型安全、加载与错误状态到useLazyQuery与client.query的选型边界、轮询与重新获取机制、六种 Fetch Policy 及其nextFetchPolicy动态调整、skipToken/skip/SSR 三类条件查询方案完成了从 API 使用到源码实现的闭环讲解。关键取舍可概括为默认场景用useQuerycache-first用户事件触发用useLazyQuery需消费其状态或client.query只需 promise实时/新鲜数据用network-only/cache-and-network并搭配nextFetchPolicy收敛后续行为条件查询优先skipToken其次skip每页保持一次查询 Hook 组件内聚 fragment的架构配合数据掩码降低重复请求。如需深入相关主题可继续阅读仓库内 Suspense Hooks 参考、fragments 参考 与 mutations 参考。赞分享前端GraphQL【免费下载链接】apollo-clientThe industry-leading GraphQL client for TypeScript, JavaScript, React, Vue, Angular, and more. Apollo Client delivers powerful caching, intuitive APIs, and comprehensive developer tools to accelerate your app development.项目地址https://gitcode.com/gh_mirrors/ap/apollo-client点击查看免费下载相关推荐Vue Apollo 查询优化终极指南如何高效使用 useQuery 和 useLazyQueryVue Apollo 查询优化终极指南如何高效使用 useQuery 和 useLazyQuery Vue Apollo 是 Vue.js 与 Grap前端GraphQLReact Apollo Hooks深度解析useQuery、useMutation实战教程React Apollo Hooks深度解析useQuery、useMutation实战教程 React Apollo Hooks是React应用中集成Gra前端Pixelle-Video 一句话生成 AI 短视频完整指南Pixelle Video 一句话生成 AI 短视频完整指南 在 Pixelle Video 里输入一句健康饮食的重要性等上 2 5 分钟一条成片短视频人工智能AI 应用音视频媒体生成创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。