- 前端
- GraphQL
【免费下载链接】apollo
🚀 Apollo/GraphQL integration for VueJS
导读
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 data | error?、isPreviousResult、partial(已废弃)、result、resultState | 数据本身:查询返回了什么、完整与否、来自哪一组变量 |
| 2. Network info | loading、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 | undefinedresult存放 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: booleanpartial描述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: booleanloading为true表示查询处于忙碌状态。文档给出的定义比直觉更宽,它覆盖三个时间段:
- 请求在飞(request in flight);
- 新变量正在等待
debounce/throttle定时器; - 两者之间的交接期:变量已被接受、请求尚未发出的瞬间。
因此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: booleanpending为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):
- 首次查询完成:
resultState === 'complete'、isPreviousResult === false; - 改变变量后立即:
result仍是'first'、resultState仍为'complete'、isPreviousResult === true、loading === true; - 新数据到达:
isPreviousResult翻回false,模板渲染'second'。
5.3 与 await 的联动
useQuery返回PromiseLike,then方法用isAwaited判定何时可以 resolve(useQuery.ts):保留结果不属于当前变量,因此isPreviousResult: true时不会提前 resolve,await 会一直等到真正针对当前变量请求的数据到达。这保证了await useQuery()与<Suspense>组合时不会拿到"上一组变量"的过期数据。
六、完整字段速查表与联动选项
6.1 Current 字段速查
| 字段 | 类型 | 分组 | 一句话语义 |
|---|---|---|---|
result | object \| null \| undefined | Operation data | 查询完成后的数据;出错时按errorPolicy可能为undefined |
resultState | complete \| partial \| empty \| streaming | Operation data | 判别器,描述result完整度,用于 TS 收窄 |
partial | boolean | Operation data | 已废弃,仅returnPartialData: true时置位,用resultState替代 |
error? | ErrorLike | Operation data | 最近一次执行的错误 |
isPreviousResult | boolean | Operation data | 结果是否来自上一组变量(keepPreviousResult) |
loading | boolean | Network info | 忙碌:请求在飞 + 防抖/节流窗口 + 交接期 |
pending | boolean | Network info | 变量已提交但请求尚未发出(仅配置debounce/throttle时) |
networkStatus | NetworkStatus | Network info | 网络状态码,pending期间保持ready |
6.2 影响 Current 的关键选项
| 选项 | 默认值 | 影响的字段 | 说明 |
|---|---|---|---|
returnPartialData | false | resultState、partial | 允许从缓存返回部分数据,resultState才会出现'partial' |
errorPolicy | none | result、error | 决定出错时是否携带部分结果 |
keepPreviousResult | false | isPreviousResult、result、resultState | 新请求在途时保留旧结果并标记isPreviousResult: true |
debounce/throttle | 无 | pending、loading | 变量更新的延迟窗口,二者互斥 |
notifyOnNetworkStatusChange | 无 | networkStatus事件 | 网络状态每次变化都触发新状态事件 |
awaitComplete | false | 配合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
相关推荐
@vue/apollo-composable 的 useQuery 深入指南:Vue 3 响应式 GraphQL 查询完全解析
@vue/apollo composable 的 useQuery 深入指南:Vue 3 响应式 GraphQL 查询完全解析 useQuery 是 @vue/
前端GraphQLmall 项目 Docker 容器化部署实战:镜像、容器、私有仓库与 Compose 编排全攻略
mall 项目 Docker 容器化部署实战:镜像、容器、私有仓库与 Compose 编排全攻略 导读 本文是一份面向 mall 电商项目(基于 Spring
前端GraphQLCSS3 属性详解(一):文本阴影、盒模型尺寸、私有前缀与边框特效
CSS3 属性详解(一):文本阴影、盒模型尺寸、私有前缀与边框特效 本文承接 CSS3 选择器详解 https://link.gitcode.com/i/3a7
前端GraphQL
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考