☰
Apollo Vue 查询状态模型:深入解析 @vue/apollo-composable 的 Current 接口
2026/10/10 2:37:54 网站建设 项目流程
  • 前端
  • GraphQL

【免费下载链接】apollo

🚀 Apollo/GraphQL integration for VueJS

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

导读

Current是@vue/apollo-composable中useQuery返回的判别联合(discriminated union)状态类型,它把"本次查询的结果数据"与"本次请求的网络状态"统一收口到一个响应式对象里,让result、resultState、loading、networkStatus、pending、isPreviousResult、error七个字段可以在模板和 TS 中同时完成类型收窄与 UI 分支渲染。本文以 Current.md 为骨架,结合 useQuery 实现源码 与 useQuery 测试用例,完整拆解每个字段的语义、取值来源与底层状态机,帮助你准确判断"数据从哪来、请求处于什么阶段",并正确使用returnPartialData、keepPreviousResult、debounce/throttle等联动选项。读完本文,你将能熟练读写current,并在模板中写出不会踩坑的加载、空态、错误与过期数据分支。

一、Current 是什么:一接口两类信息

从类型定义看,Current是由 Apollo 上报的结果状态与 Vue-Apollo 自建状态合并而成的:

// packages/vue-apollo-composable/src/useQuery.ts export type Current<TData, TStates> = ResultState<TData, TStates> & VueApolloState

其中ResultState把 Apollo ClientObservableQuery.Result中的data/dataState重命名为 Vue-Apollo 风格的result/resultState(见 useQuery.ts 与toResultState辅助函数),VueApolloState则是 Vue-Apollo 自己跟踪的pending与isPreviousResult(useQuery.ts)。

文档将全部字段分成两组:

分组字段描述对象
1. Operation dataerror?、isPreviousResult、partial(已废弃)、result、resultState数据本身:查询返回了什么、完整与否、来自哪一组变量
2. Network infoloading、networkStatus、pending请求本身:有没有请求在飞、处于防抖/节流窗口还是已发出

理解这条分界线是正确使用current的关键:result相关的字段描述"你正在展示的数据",loading/networkStatus/error描述"正在替换它的请求"。isPreviousResult存在的原因正是为了让这两组信息可以同时成立(见后文第五节)。

在useQuery的返回对象中,current是以Readonly<Ref<Current<TData, TStates>>>形式暴露的响应式状态(useQuery.ts),模板里直接读写current.loading、current.resultState即可,无需手动解包。

二、Operation data:查询结果说了什么

2.1 result:查询完成后的数据对象

result: object | null | undefined

result存放 GraphQL 查询完成后返回的数据对象。文档特别提醒:当查询产生一个或多个错误时,result可能为undefined——具体行为取决于查询的errorPolicy选项:

  • errorPolicy: 'none'(默认):结果中包含错误信息,但不包含部分结果,此时result通常为undefined;
  • errorPolicy: 'all':同时返回错误与部分数据;
  • errorPolicy: 'ignore':忽略错误,直接使用部分数据。

在 Options 接口 中可以看到该选项的完整说明。与useLazyQuery的FetchMoreResult一样,这里遵循 Vue-Apollo 的命名惯例:Apollo 里的data在 Vue-Apollo 中一律叫result。

2.2 resultState:结果的完整度判别器

resultState: "complete" | "partial" | "empty" | "streaming"

resultState是Current的判别字段(discriminator),它精确描述result的完整程度,四个取值互斥:

取值含义result状态
empty缓存未能满足任何数据,或结果不完整undefined
partial缓存满足了部分字段,但结果仍不完整部分数据,仅在returnPartialData: true时可能出现
streaming因@defer/@stream延迟查询,数据仍在陆续到达不完整但持续增长
complete结果已完全满足(来自缓存或网络)完整数据

这四个取值正是 streaming 文档 中@defer查询生命周期的基础:先empty,第一块数据到达后变streaming,服务端宣告结束才变complete。

resultState最重要的价值是类型收窄。因为result在empty时是undefined,只有借助判别联合才能让 TS 推断出安全的数据类型,这也是官方推荐"读取result一律走current"的原因(queries 文档):

const { current } = useQuery(GetUser) if (current.value.resultState === 'complete') { // 此处 current.value.result 被收窄为 TData,可安全访问嵌套字段 console.log(current.value.result.user.name) }

在模板中同样可以按resultState分支渲染,测试用例里也验证了这一模式(useQuery.test.ts):

<div v-if="current.loading">Loading...</div> <div v-else-if="current.resultState === 'complete'"> {{ current.result.hello }} </div>

2.3 partial:已废弃的兼容字段

/** @deprecated */ partial: boolean

partial描述result是完整结果还是部分结果,仅在returnPartialData: true时才会被置位。文档明确标注:该字段将在 Apollo Client 的未来版本中被移除,其职能已由更精确的resultState('partial'取值)完全取代。新代码应直接使用resultState === 'partial'判断,不要依赖partial。

2.4 error:最近一次执行的错误

error?: ErrorLike

单个ErrorLike对象,描述最近一次查询执行期间发生的错误。它与resultState的联动语义值得注意:errorPolicy: 'none'时,出错会让result变为undefined;而isPreviousResult: true的保留结果场景中,error描述的是"正在替换旧数据的那次请求",详见第五节。

从源码实现看,error通过currentState统一维护(useQuery.ts),并在applyState保留结果时被特殊处理:只有新状态确实携带error时才覆盖旧值(useQuery.ts)。

三、Network info:请求本身处于什么阶段

3.1 loading:查询是否"忙"

loading: boolean

loading为true表示查询处于忙碌状态。文档给出的定义比直觉更宽,它覆盖三个时间段:

  1. 请求在飞(request in flight);
  2. 新变量正在等待debounce/throttle定时器;
  3. 两者之间的交接期:变量已被接受、请求尚未发出的瞬间。

因此loading比networkStatus < 7更宽泛——后者只描述请求本身。源码中的实现印证了这一点:

// packages/vue-apollo-composable/src/useQuery.ts const loading = computed(() => pending.value || isCommitting.value || currentState.value.loading)

loading由三部分合成:pending(防抖/节流等待窗口)、isCommitting(变量已提交但还没进入下一次刷新)、以及 Apollo 上报的loading。这意味着只要变量发生变化,loading立刻为true,搜索框打字即显示 spinner,而不用等防抖结束(queries 文档)。

3.2 pending:防抖/节流窗口的专属标志

pending: boolean

pending为true表示:variables已变化、请求已"承诺"(committed)发出,但因debounce或throttle选项尚未真正发到网络。loading虽然也覆盖这个窗口,但pending让你能区分"定时器还没走完"与"请求真的在线上"——例如做请求指标统计、展示"取消"按钮等场景。

源码中pending的实现非常直接:

// packages/vue-apollo-composable/src/useQuery.ts const pending = computed(() => { const { debounce, throttle } = vueApolloQueryOptions.value if (debounce == null && throttle == null) return false return !equal(variables.value, currentVariables.value) })

只有配置了debounce或throttle时pending才可能为true,且深等比较(@wry/equality的equal)保证:重建出深等内容的变量不会报为 pending(因为没有请求会发出),在定时器走完前变回去的变量同样不会报为 pending(queries 文档)。

注意一个细节:pending为true期间,networkStatus仍保持ready——网络状态只描述网络本身。这是文档明确强调的:"it staysreadywhilependingistrue"。

3.3 networkStatus:底层网络状态码

networkStatus: NetworkStatus

数字型网络状态码,描述查询关联请求当前的网络阶段(可能取值见 Apollo Client 的networkStatus.ts)。典型取值包括:

状态含义
ready空闲(idle),可能刚完成或尚未开始
loading初次加载中
refetch重新请求中(networkStatus < 7时loading为真)
poll轮询中
fetchMore分页加载更多中
streaming增量传输中

networkStatus与pending互补:它只描述网络,在pending为true期间保持ready。文档建议它与notifyOnNetworkStatusChange选项配合使用——开启该选项后,网络状态每次变化都会触发新的状态事件(Options 文档),模板中即可区分"初次加载"与"刷新中":

<div v-if="current.networkStatus === NetworkStatus.refetch"> Refetching... </div>

四、source 视角:Current 状态机是如何被驱动的

理解了字段语义后,再看 useQuery.ts 内部如何生产这些字段,能避免很多使用陷阱。

4.1 单一状态源:currentState

所有 Operation data 与 Apollo 上报的网络字段先汇总进一个shallowRef:

// packages/vue-apollo-composable/src/useQuery.ts const currentState = shallowRef<useQuery.ResultState<TData> & Pick<useQuery.VueApolloState, 'isPreviousResult'>>({ result: undefined, loading: false, networkStatus: NetworkStatus.ready, resultState: 'empty', partial: false, isPreviousResult: false, })

current计算属性在其上叠加pending与合成后的loading,并通过isSameState做对象身份复用:当没有任何字段实际变化时返回上一次的对象,避免watch(current)和onNextState误报"变化"([useQuery.ts](https://link.gitcode.com/i/773228f6a9418d2a913f4001e13a2b67#L945-L950, L1182-L1196))。

4.2 Apollo 状态进入 Vue 的桥:applyState

ObservableQuery 的每次通知都会经过toResultState改名后进入applyState(useQuery.ts)。该函数承担keepPreviousResult的核心逻辑:

if ( newState.resultState === 'empty' && vueApolloQueryOptions.value.keepPreviousResult && previousState.resultState !== 'empty' ) { // 保留旧 result / resultState / partial,覆盖 loading / networkStatus / error currentState.value = { ...retainedResult, ...(newState.error !== undefined && { error: newState.error }), loading: newState.loading, networkStatus: newState.networkStatus, isPreviousResult: true, } return } currentState.value = { ...newState, isPreviousResult: false }

这正是第五节要展开的核心机制:保留结果时,result、resultState、partial三个字段整体保持旧值(所以按resultState收窄永远安全),而loading、networkStatus、error来自新请求。

4.3 响应式选项的分流

源码将选项拆成两部分分别响应(useQuery.ts):

  • Apollo 原生选项(fetchPolicy、errorPolicy、returnPartialData、pollInterval、notifyOnNetworkStatusChange等)进入watchQueryOptions,最终随apolloWatchQueryOptions传给client.watchQuery或触发reobserve;
  • Vue-Apollo 专属选项(clientId、enabled、throttle、debounce、prefetch、keepPreviousResult、awaitComplete)进入vueApolloQueryOptions,直接驱动pending、loading、applyState等内部逻辑。

debounce/throttle通过useDebounceFn/useThrottleFn实现,变量变化被 watch 捕获后按配置分流提交(useQuery.ts)。测试用例验证了两种行为:debounce时快速连续更新只会发出最后一次("Rapid updates - only last should go through after debounce"),throttle则按 leading + trailing 边沿发出(useQuery.test.ts)。

五、实战聚焦:isPreviousResult 与 keepPreviousResult

5.1 语义:旧数据与替换请求并存

isPreviousResult: boolean

当keepPreviousResult: true时,变量变化后的新请求在途期间,result会保留上一组变量的数据。此时:

  • resultState、result、partial描述被保留的旧数据——因此按resultState收窄永远安全;
  • loading、networkStatus、error描述正在替换它的新请求;
  • isPreviousResult: true让你能区分"这是不是新数据"。

文档强调:"narrowing onresultStateis always safe",因为保留逻辑保证result/resultState/partial三者同步移动(见 4.2 的applyState),isPreviousResult只负责标注来源。

5.2 典型模板写法

从 queries 文档 的示例可以看到推荐用法——给旧数据加"过期"样式:

<script setup lang="ts"> const term = ref('') const { current } = useQuery(SearchProducts, { variables: { term }, keepPreviousResult: true, }) </script> <template> <ul v-if="current.resultState === 'complete'" :class="{ stale: current.isPreviousResult }"> <li v-for="product in current.result.products" :key="product.id"> {{ product.name }} </li> </ul> <p v-else-if="!current.loading">No products found.</p> </template>

这一模式对筛选器、分页尤其有价值:每次变量变化不再闪空态,而是继续展示旧数据直到新数据到达。测试用例完整覆盖了状态迁移(useQuery.test.ts):

  1. 首次查询完成:resultState === 'complete'、isPreviousResult === false;
  2. 改变变量后立即:result仍是'first'、resultState仍为'complete'、isPreviousResult === true、loading === true;
  3. 新数据到达:isPreviousResult翻回false,模板渲染'second'。

5.3 与 await 的联动

useQuery返回PromiseLike,then方法用isAwaited判定何时可以 resolve(useQuery.ts):保留结果不属于当前变量,因此isPreviousResult: true时不会提前 resolve,await 会一直等到真正针对当前变量请求的数据到达。这保证了await useQuery()与<Suspense>组合时不会拿到"上一组变量"的过期数据。

六、完整字段速查表与联动选项

6.1 Current 字段速查

字段类型分组一句话语义
resultobject \| null \| undefinedOperation data查询完成后的数据;出错时按errorPolicy可能为undefined
resultStatecomplete \| partial \| empty \| streamingOperation data判别器,描述result完整度,用于 TS 收窄
partialbooleanOperation data已废弃,仅returnPartialData: true时置位,用resultState替代
error?ErrorLikeOperation data最近一次执行的错误
isPreviousResultbooleanOperation data结果是否来自上一组变量(keepPreviousResult)
loadingbooleanNetwork info忙碌:请求在飞 + 防抖/节流窗口 + 交接期
pendingbooleanNetwork info变量已提交但请求尚未发出(仅配置debounce/throttle时)
networkStatusNetworkStatusNetwork info网络状态码,pending期间保持ready

6.2 影响 Current 的关键选项

选项默认值影响的字段说明
returnPartialDatafalseresultState、partial允许从缓存返回部分数据,resultState才会出现'partial'
errorPolicynoneresult、error决定出错时是否携带部分结果
keepPreviousResultfalseisPreviousResult、result、resultState新请求在途时保留旧结果并标记isPreviousResult: true
debounce/throttle无pending、loading变量更新的延迟窗口,二者互斥
notifyOnNetworkStatusChange无networkStatus事件网络状态每次变化都触发新状态事件
awaitCompletefalse配合resultState === 'streaming'await useQuery()是否等待完整数据(用于@defer/@stream)

以上选项的完整定义见 Options 接口文档,默认值均可在源码的Base.VueApolloOptions与Base.Options注释中核对(useQuery.ts)。

6.3 组合案例:一个完整的查询状态分支

综合所有字段,一个健壮的模板分支可以这样组织:

<script setup lang="ts"> const { current } = useQuery(GetPosts, { variables: { term }, debounce: 300, keepPreviousResult: true, }) </script> <template> <!-- 防抖窗口 + 请求在飞 + 交接期都算 loading --> <div v-if="current.loading && current.resultState === 'empty'">Loading...</div> <!-- 保留旧数据时展示,并标记过期 --> <div v-else-if="current.resultState === 'complete'" :class="{ stale: current.isPreviousResult }"> <p v-if="current.pending">正在等待防抖…</p> <ul> <li v-for="post in current.result.posts" :key="post.id">{{ post.title }}</li> </ul> </div> <div v-else-if="current.error">{{ current.error.message }}</div> <div v-else>No results.</div> </template>

七、延伸阅读

  • useQuery 接口总览:Current所属命名空间的全部接口与类型别名;
  • Queries 指南:useQuery基础用法、变量响应式、缓存与 fetch policy;
  • Loading States:跨多个查询聚合loading的useQueryLoading/useGlobalQueryLoading等组合式函数;
  • Streaming & @defer:resultState === 'streaming'的完整生命周期与awaitComplete用法;
  • 实现源码:currentState、applyState、pending、loading与isAwaited的具体实现;
  • 测试用例:覆盖keepPreviousResult、returnPartialData、debounce/throttle、流式结果等场景的状态迁移验证。
  • 前端
  • GraphQL

【免费下载链接】apollo

🚀 Apollo/GraphQL integration for VueJS

项目地址:https://gitcode.com/gh_mirrors/apollo2/apollo
点击查看免费下载
上一篇:KernelSU x86_64 支持详解:syscall 表加固的兼容方案与 `KSU_X86_PATCH_SYSCALL_DISPATCHER` 实战
下一篇:Ingress-Nginx Controller 的 Kubernetes RBAC 权限模型:ServiceAccount、Role 与 ClusterRole 完整解读

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

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

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

立即咨询