☰
Apollo 2 系列:Vue 中 useLazyQuery 按需查询组合式函数完全指南
2026/10/10 2:12:15 网站建设 项目流程
  • 前端
  • GraphQL

【免费下载链接】apollo

🚀 Apollo/GraphQL integration for VueJS

项目地址:https://gitcode.com/gh_mirrors/apollo2/apollo
点击查看免费下载

useLazyQuery是@vue/apollo-composable(本项目 Apollo/GraphQL 与 Vue 的集成层)中用于按需执行 GraphQL 查询的组合式函数。它适用于变量在组件挂载时未知、必须等用户操作(搜索提交、按钮点击、弹窗打开)后才发起请求的场景。读完本文,你将掌握load()触发机制、完整 Options/Result 契约、错误处理与 SSR 行为,并能基于源码理解其底层实现原理。

为什么需要 useLazyQuery:与 useQuery 的分工

在 packages/docs/advanced/lazy-queries.md 中,官方给出了三者的选择矩阵:

场景工具
变量来自用户动作(搜索框提交、按钮点击)useLazyQuery
变量已知,但希望按条件控制是否执行useQuery({ enabled })
变量已知,且挂载后立即执行useQuery

其核心区别在于:useQuery一旦调用就会立即发起请求,而useLazyQuery初始处于禁用状态,必须显式调用它暴露的load(variables?)函数才会启动。一旦第一次load完成,查询便退化为普通的useQuery行为——变量变为响应式、结果自动更新、作用域销毁时自动停止。

从源码看,这一“先禁用后激活”的实现位于 packages/vue-apollo-composable/src/useLazyQuery.ts:useLazyQuery本身只是对内部实现useQueryImpl(query, options, true)的薄封装,其中第三个参数lazy = true。在 packages/vue-apollo-composable/src/useQuery.ts 中,lazy标志被转换为内部 refforceDisabled(const forceDisabled = ref(lazy)),并参与最终启用态的计算:

const isEnabled = computed(() => enabledOption.value && !forceDisabled.value && !!document.value)

也就是说,只要forceDisabled为true,watch(isEnabled)就不会创建ObservableQuery(observableQuery.value = client.watchQuery(...)不会执行),请求自然不会被发出;直到load()调用start()(forceDisabled.value = false)后,查询才真正激活。

基本用法:从文档示例到可运行代码

官方函数文档(packages/docs/api/composable/functions/useLazyQuery.md)给出了最简示例。下面是一个结合响应式模板的完整形态:

<script setup lang="ts"> import { TypedDocumentNode } from '@apollo/client' import { useLazyQuery } from '@vue/apollo-composable' import { ref } from 'vue' const SearchUsers: TypedDocumentNode<{ users: { id: string, name: string }[] }, { term: string }> = gql` query SearchUsers($term: String!) { users(search: $term) { id name } } ` const term = ref('') const { load, current } = useLazyQuery(SearchUsers) async function search() { const result = await load({ term: term.value }) console.log('Found users:', result?.users) } </script> <template> <form @submit.prevent="search"> <input v-model="term"> <button>Search</button> </form> <div v-if="current.loading"> Searching... </div> <ul v-else-if="current.resultState === 'complete'"> <li v-for="user in current.result.users" :key="user.id"> {{ user.name }} </li> </ul> </template>

要点:

  • query参数类型为MaybeRefOrGetter<DocumentNode | TypedDocumentNode<TData, TVariables>>,即可以传文档本身,也可以传一个返回文档的 ref 或 getter;
  • options?参数类型同样为MaybeRefOrGetter<Options>,支持响应式配置;
  • 返回值Result提供响应式 refs 与load函数(详见下文 Result 章节);
  • load(variables?)的行为是:把传入变量合并进已有变量(源码中为variablesRef.value = { ...variablesRef.value, ...variables }),然后调用start()启动查询,返回一个 Promise——查询完成时以结果数据 resolve,出错时以错误 reject。

结果除了出现在load的返回值里,也会同时落入current.result以及result、loading、error等独立的响应式 refs 中,与useQuery的用法完全一致。

多次加载:变量合并语义

load可以被重复调用,每次调用都会用新变量重新执行查询:

const { load } = useLazyQuery(SEARCH_USERS) await load({ term: 'alice' }) // 第一次搜索 await load({ term: 'bob' }) // 新搜索

每次调用都会把新变量“叠加”在已加载的变量之上(merge 而非 replace)。因此如果第一次传了{ term: 'alice' },第二次传{ limit: 10 },最终发送的变量会是{ term: 'alice', limit: 10 }。

需要留意的是源码中的超时与等待逻辑:load内部使用@vueuse/core的until(...).toBe(true, { timeout: 30000 })等待当前状态变为resultState === 'complete'(且非isPreviousResult)或出现error,超时上限 30 秒;随后若存在错误则直接 throw,否则返回queryResult.result.value。这解释了为什么load的 Promise 既可能 resolve 数据,也可能 reject 错误。

Options:完整的配置契约

useLazyQuery的选项与useQuery完全一致,唯一例外是enabled不可用——因为查询在load()调用之前永远是懒的。从源码类型定义看(packages/vue-apollo-composable/src/useLazyQuery.ts):

export type Options<TData, TVariables> = Omit<useQuery.Options<TData, TVariables>, 'enabled'>

选项按用途分为四组(完整参考:Options.md)。

1. 操作选项(Operation options)

选项类型默认值说明
errorPolicyErrorPolicynone决定查询在同时返回 GraphQL 错误与部分结果时如何处理;none表示结果包含错误详情但不含部分数据
variablesReactiveVariablesParameter—查询所需的全部 GraphQL 变量对象,每个 key 对应变量名,值对应变量值

其中variables支持多种响应式形态(见 useQuery.ts 中ReactiveVariablesParameter的定义):可以传整个变量的 ref/getter,也可以传一个“逐个变量映射到 ref/getter”的对象,例如:

const id = ref(1) useQuery(query, { variables: { id } })

useLazyQuery场景下,options.variables充当load()的默认变量:先调用load()不传参时使用选项中的响应式变量,调用load({...})传参时则将其合并覆盖。

2. 网络选项(Networking options)

选项类型默认值说明
contextDefaultContext—若使用 Apollo Link,作为沿 link 链传递的context对象的初始值
notifyOnNetworkStatusChangebooleanfalse为true时,每当网络状态变化或发生网络错误就触发下一次状态事件
pollIntervalnumber0(不轮询)查询轮询更新结果的间隔(毫秒)
skipPollAttempt()() => boolean—轮询期间每次尝试 refetch 前调用;返回true则跳过本次 refetch,直到下一个轮询周期再试

3. 缓存选项(Caching options)

选项类型默认值说明
fetchPolicyWatchQueryFetchPolicycache-first查询与 Apollo Client 缓存的交互方式(是否先查缓存再发请求)
initialFetchPolicyWatchQueryFetchPolicy跟随fetchPolicy指定变量变化时应回退到的策略(除非nextFetchPolicy介入)
nextFetchPolicy策略或回调—本次查询完成之后使用的FetchPolicy
refetchWritePolicyRefetchWritePolicymerge(兼容 Apollo Client 3.x)NetworkStatus.refetch操作写入缓存时,是合并现有字段数据还是整体覆盖;覆盖通常更合适
returnPartialDatabooleanfalse为true时,若缓存未包含全部查询字段,允许返回部分结果

4. Vue-Apollo 专属选项(Vue-Apollo options)

选项类型默认值说明
awaitCompletebooleanfalse是否等待数据完整后再 resolve;配合@stream/@defer指令使用,仅影响 SSR 预取与await useQuery()
clientIdstring—指定要使用的具名 Apollo Client 的 ID(替代默认 client)
debouncenumber—变量更新的防抖毫秒数
keepPreviousResultbooleanfalse加载新数据时保留上一份结果;保留结果仍以正常结果上报(resultState、result、partial描述它),并带isPreviousResult: true标记以便区分
prefetchbooleantrue是否在服务端预取
throttlenumber—变量更新的节流毫秒数

从 useQuery.ts 的实现可以看到这些选项如何生效:Apollo 相关选项(fetchPolicy、context、pollInterval等)被提取为watchQueryOptionscomputed,最终与query、variables合并成传给client.watchQuery()的完整选项;Vue-Apollo 专属选项则被提取为vueApolloQueryOptions,其中debounce/throttle通过useDebounceFn/useThrottleFn(flush: 'sync'的 watcher)延迟currentVariables的提交,pending标志会在这段窗口期内保持为true。

Result:完整的返回值契约

useLazyQuery的返回值Result继承自useQuery.Result,并额外扩展了load函数(完整参考:Result.md)。按用途可分为六组。

1. 操作数据(Operation data)

字段类型说明
currentRef<Current>当前状态的判别联合类型(discriminated union)
errorRef<ErrorLike \| undefined>最近一次查询执行中发生的错误
isPreviousResultRef<boolean>为true时,result是keepPreviousResult保留的旧变量结果,不对应当前variables;此时resultState/result/partial描述保留结果,loading/networkStatus/error描述正在替换它的请求
resultRef<object \| null>查询完成后的结果对象;若查询产生一个或多个错误,可能为undefined(取决于errorPolicy)

current中的resultState取值为'empty' | 'complete' | 'streaming' | 'partial':empty表示缓存或网络都无法提供数据;partial仅在returnPartialData: true时出现;streaming表示延迟查询(@defer)仍在流式返回中;complete表示结果是完整满足的。

2. 网络信息(Network info)

字段类型说明
loadingRef<boolean>查询忙碌中:覆盖请求在途、变量等待debounce/throttle计时器,以及变量已接受但请求尚未发出的交接期。比networkStatus < 7的范围更广
networkStatusRef<NetworkStatus>查询请求的网络状态编号;只描述网络本身——pending为true时它仍保持ready。与notifyOnNetworkStatusChange配合使用
pendingRef<boolean>为true时,variables已变化且请求已提交,但因debounce/throttle尚未发出;loading同样覆盖这段窗口,用pending可区分“计时器未到”与“请求真的在网络上”

3. Refs(引用)

字段类型说明
documentRef<DocumentNode>正在查询的 GraphQL 文档
optionsRef<Options>当前选项
queryRef<ObservableQuery \| undefined>底层 Apollo ObservableQuery 实例;查询停止时为undefined
variablesRef<OperationVariables>正在发送给查询的变量(经过 debounce/throttle 之后)

4. 生命周期(Lifecycle)

  • load(variables?):加载查询。若未启动则启动之;传入的变量会与选项中的变量合并。返回Promise<object | undefined>,查询完成时 resolve 数据,出错时 reject。useLazyQuery的独有方法,也是它区别于useQuery的关键;
  • start():启动查询;若查询已激活或enabled为false则无效果;
  • stop():停止查询,可随时通过start()重新启动;
  • restart():停止并重启查询,返回Promise<void>。

5. 查询方法(Query methods)

  • fetchMore(options):为分页/无限滚动加载更多数据,完成后响应式 refs 自动更新。数据合并有两种方式:一是updateQuery回调手动合并fetchMoreResult与previousQueryResult;二是在 Apollo 缓存配置中定义字段级merge函数。注意:若使用fetchPolicy: 'no-cache',必须提供updateQuery。
const { result, fetchMore } = useQuery(GetPosts, { variables: { offset: 0, limit: 10 } }) async function loadMore() { await fetchMore({ variables: { offset: result.value.posts.length }, updateQuery: (previousQueryResult, { fetchMoreResult }) => ({ ...previousQueryResult, posts: [...previousQueryResult.posts, ...fetchMoreResult.posts] }) }) }
  • refetch(variables?):可选携带新变量重新执行查询,适合“刷新”按钮、下拉刷新等命令式场景。注意两点:
    • 需要与响应式变量保持同步的变量应放在options.variables中(如路由参数、表单输入),一次性命令式获取才用refetch();
    • 传入refetch的变量是临时的:仅用于这一次请求,暴露的variablesref 不会更新;之后响应式options.variables变化时,查询会使用选项中的值而非 refetch 传入的值。
await refetch() // 用当前变量重新执行 await refetch({ id: 'other-user' }) // 仅本次使用的新变量
  • subscribeToMore(options):通过 GraphQL subscription 为查询增加实时数据,订阅数据到达时由updateQuery合并进现有结果。订阅在查询停止或组件卸载时自动清理,也可调用返回的函数手动取消:
const unsubscribe = subscribeToMore({ document: OnMessageAdded, variables: { channelId }, updateQuery: (_, { previousData, subscriptionData }) => { if (!subscriptionData.data) return previousData return { ...previousData, messages: [...previousData.messages, subscriptionData.data.messageAdded] } } })
  • updateQuery(mapFn):直接更新缓存的查询结果,适用于乐观更新或免网络请求的数据修改,写入缓存后响应式 refs 自动更新。文档建议谨慎使用:多数缓存更新应优先考虑缓存字段策略或client.writeQuery,updateQuery绕过了 Apollo 常规缓存更新机制,适合快速本地修改:
function addOptimisticTodo(todo: Todo) { updateQuery((_, { previousData }) => ({ ...previousData, todos: [...previousData.todos, todo] })) }

6. 事件(Events)

事件触发时机
onNextState最细粒度事件,每次状态更新都会触发(含 loading 状态、网络状态变化、结果更新)
onResult收到查询结果数据时;resultState为'complete'、'partial'、'streaming'时触发,'empty'状态与错误不触发
onCompleteResult仅当resultState为'complete'时触发
onPartialResult仅当resultState为'partial'时触发(需要returnPartialData: true)
onStreamingResult仅当resultState为'streaming'时触发(配合@defer/@stream指令)
onError查询发生错误时触发

事件底层由@vueuse/core的createEventHook实现(见 useQuery.ts 的 Events 区域),事件分发通过triggerResultEvents依据resultState路由到对应 hook。

等待结果与错误处理

load返回 Promise,因此可以内联使用结果:

async function fetchUser(id: string) { try { const data = await load({ id }) if (data) { console.log('Loaded:', data.user) } } catch (e) { console.error('Failed to load:', e) } }

Promise 在结果完整时以查询数据 resolve;查询出错时以错误 reject。无论成败,响应式 refs(current、result、error)都会同步更新。这一行为由源码中的等待循环保证:load等待resultState === 'complete'(且非保留结果)或error != null二者之一,超时 30 秒;一旦queryResult.error.value存在即 throw。

SSR 场景下的行为

useLazyQuery不会在服务端运行——因为服务端不存在触发它的用户动作。如果某个值需要在 SSR 阶段就可用,请改用useQuery(参见 packages/docs/ssr/overview.md)。从源码也可印证:useQueryImpl中注册onServerPrefetch时要求isEnabled.value为真才会预取,而懒查询在服务端渲染阶段forceDisabled仍为true,故不会触发预取。

兼容层:与 v4 行为的映射

仓库中还有一套兼容实现 packages/vue-apollo-composable/src/compat/useLazyQuery.ts,用于对齐 v4 的useLazyQuery语义,差异值得了解:

  • load 签名:load(document?, variables?)可携带文档覆盖与变量覆盖;v4 语义下变量是替换(replace)而非 v5 的合并(merge);
  • 重复调用:首次调用启动查询并返回 Promise;后续调用返回false而不重新执行。要换变量重新查询,需要修改响应式变量 ref/getter 或调用refetch(variables);
  • 错误策略:errorPolicy为all或ignore时,v5 的onError会先于 compat 的onResult触发,compat 层用queueMicrotask调整顺序,保证与 v4 一致地优先 resolve。

小结与延伸阅读

useLazyQuery的定位非常明确:当变量来自用户动作、无法在挂载时确定时,用它替代useQuery;一旦load()触发,它就完全复用useQuery的响应式机制、缓存策略与查询方法,无需学习两套心智模型。其实现简洁优雅——useLazyQuery仅约 40 行,核心是useQueryImpl(..., true)加上一个合并变量并等待完成的load()。

相关仓库资源:

  • 实现源码:packages/vue-apollo-composable/src/useLazyQuery.ts
  • 底层实现:packages/vue-apollo-composable/src/useQuery.ts
  • 进阶指南:packages/docs/advanced/lazy-queries.md
  • API 参考:useLazyQuery 函数、Options、Result
  • 查询基础:packages/docs/data/queries.md、重新获取与轮询对比:packages/docs/data/refetching.md、多查询加载状态:packages/docs/advanced/loading-states.md
  • 前端
  • GraphQL

【免费下载链接】apollo

🚀 Apollo/GraphQL integration for VueJS

项目地址:https://gitcode.com/gh_mirrors/apollo2/apollo
点击查看免费下载

相关推荐

上一篇:如何在3分钟内为Unity游戏安装XUnity.AutoTranslator:终极实时翻译插件指南
下一篇:XUnity Auto Translator:Unity游戏翻译的终极解决方案,让外语游戏无障碍畅玩

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询