Apollo Client 版本演进全解读:从 4.0 架构重构到 4.2 类型安全与事件驱动 Refetch
【免费下载链接】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
这篇技术指南以仓库根目录的 CHANGELOG.md(覆盖 Apollo Client 2.3.2 至 4.2.12 的全部版本记录)为核心脉络,系统梳理 Apollo Client 4.x 系列带来的架构级变革:框架无关核心、统一错误处理、dataState结果状态、可插拔增量交付、classic/modern 双签名类型系统,以及 4.2 全新引入的事件驱动自动 Refetch(RefetchEventManager)。读完本文,你将理解每个版本"为什么改、改了什么、如何迁移",并能直接对照仓库源码(src/core、src/errors、src/incremental、src/local-state等)验证底层实现。
一、CHANGELOG 覆盖范围与阅读方法
当前仓库的 CHANGELOG.md 是一份累计超过 1 万行的完整版本历史,覆盖从 2018 年的2.3.2到当前最新的4.2.12:
- 4.2.x 系列:最新的补丁与次要版本(当前头部),包含类型系统现代化与事件驱动 Refetch 两大主线;
- 4.0.0 大版本:一次完整的架构重构,文内含独立的Release Notes专题章节(见 CHANGELOG.md 4.0.0 一节),是理解现代 Apollo Client 设计哲学的核心材料;
- 3.x / 2.x 系列:早期历史,记录了大量缓存、
ObservableQuery、fetch policy 相关的修复,对排查历史问题仍有参考价值。
版本条目统一采用 Changesets 格式:每个条目包含 PR 链接、提交哈希、贡献者与变更说明,并用### Minor Changes(新特性)与### Patch Changes(修复)分类。下文聚焦最有价值的 4.x 主线展开。
二、Apollo Client 4.0:一次彻底的架构重构
4.0.0 的 Release Notes 明确将其定位为"更现代、更高效、更类型安全的 GraphQL 客户端体验",核心围绕开发者体验、包体积优化与框架灵活性三个方向。
2.1 框架无关的核心(Framework-Agnostic Core)
4.0 将 React 功能与核心库分离:React 相关导出统一收归@apollo/client/react,核心不再依赖 React。这意味着你可以在任意 JavaScript 框架中使用 Apollo Client 而不会引入 React 依赖。仓库中 src/react/index.ts 与 src/core/ApolloClient.ts 的目录划分正是这一架构的落地体现。
2.2 更小的包体积
- 局部状态按需引入:
@client指令能力改为通过LocalState类按需启用(源码见 src/local-state/LocalState.ts),不使用局部状态时不会被打包; - 现代构建目标:转译目标为
since 2023, node >= 20, not dead,充分利用现代 JavaScript 特性; - 更优的 Tree-Shaking:
package.json中规范的exports字段让死代码消除更彻底。
2.3 统一错误处理(Unified Error Handling)
4.0 移除了ApolloError,改用一组更细粒度的错误类,并统一收敛到单一的error属性:
| 错误类 | 语义 |
|---|---|
CombinedGraphQLErrors | 服务器返回的 GraphQL 错误(源码见 src/errors/CombinedGraphQLErrors.ts) |
ServerError | 非 GraphQL 的服务器错误 |
ServerParseError | 服务器响应解析错误 |
UnconventionalError | 非 Error 对象被 throw 时的包装 |
LinkError | 链路(Link)抛出的错误,通过.is()判定 |
这些类都提供静态.is()方法用于类型收窄,网络错误也遵循errorPolicy设置,外部错误则原样透传不再包装。迁移示例:
// Apollo Client 3 if (error instanceof ApolloError) { console.log(error.graphQLErrors); console.log(error.networkError); } // Apollo Client 4 import { CombinedGraphQLErrors } from "@apollo/client"; if (CombinedGraphQLErrors.is(error)) { console.log(error.errors); // GraphQL errors } else if (error) { console.log(error.message); // Other errors }2.4 全新的dataState属性
dataState清晰标识查询结果的完整性,是 4.0 类型系统的基石之一:
empty:无数据(data为undefined);partial:returnPartialData为true时从缓存读到的部分数据;streaming:@defer查询仍在流式返回中的不完整数据;complete:完全满足的查询结果。
const { data, dataState } = useQuery(MY_QUERY); if (dataState === "complete") { // TypeScript 知道 data 字段是完整的 console.log(data.allFields); } else if (dataState === "partial") { // TypeScript 知道 data 可能缺字段 console.log(data?.someField); }2.5 可插拔的增量交付(@defer/@stream)
4.0 将增量交付设计为可配置、面向未来的机制,通过incrementalHandler注入。仓库中的导出见 src/incremental/index.ts:
import { Defer20220824Handler } from "@apollo/client/incremental"; const client = new ApolloClient({ // ... incrementalHandler: new Defer20220824Handler(), });NotImplementedHandler:默认处理器,一旦使用@defer直接抛出异常;Defer20220824Handler:支持 Apollo Router 的增量格式,同时以GraphQL17Alpha2Handler别名导出。
2.6 局部状态管理(LocalState 类)
@client字段解析改为显式启用:
import { LocalState } from "@apollo/client/local-state"; const client = new ApolloClient({ cache, localState: new LocalState({ resolvers: { Query: { myField: () => "Hello World", }, }, }), });同时 resolver 的context结构发生变化:4.0 中context提供client、requestContext、phase三个字段,cache改为通过client.cache获取:
// Apollo Client 4 const resolver = (parent, args, context, info) => { const { client, requestContext, phase } = context; const cache = client.cache; };2.7 React 侧改进
useLazyQuery重构:不再接受variables/context选项(改由execute接收),execute只接受variables与context;禁止在渲染期间或 SSR 中调用;新查询启动时自动取消在途查询;useMutation:移除ignoreResults选项(如需 fire-and-forget 直接使用client.mutate);useQuery:notifyOnNetworkStatusChange默认改为true;移除废弃的onCompleted/onError回调;- 新 SSR API
prerenderStatic:取代旧的 SSR 函数,支持 React 19 的 prerender API:
import { prerenderStatic } from "@apollo/client/react/ssr"; const html = await prerenderStatic(<App />, { client, });- React Compiler 支持:提供
@apollo/client/react/compiled入口(见 src/react/index.ts 中 compiled 运行时),内置 React 17+ 兼容的运行时 polyfill。
2.8 Link 体系演进:全部类化
创建函数(creator function)统一迁移为类:
// Apollo Client 3 import { createHttpLink, setContext } from "@apollo/client"; const httpLink = createHttpLink({ uri: "/graphql" }); const authLink = setContext((operation, prevContext) => { /*...*/ }); // Apollo Client 4 import { HttpLink, SetContextLink } from "@apollo/client"; const httpLink = new HttpLink({ uri: "/graphql" }); const authLink = new SetContextLink((prevContext, operation) => { /*...*/ });ErrorLink也同步改为单一error属性与错误类.is()判定(对应仓库 src/link/error/index.ts)。
2.9 迁移工具与破坏性变更清单
4.0 提供自动化 codemod:
# 基础用法 npx @apollo/client-codemod-migrate-3-to-4 src # TypeScript 项目(分开执行) npx @apollo/client-codemod-migrate-3-to-4 --parser ts --extensions ts src npx @apollo/client-codemod-migrate-3-to-4 --parser tsx --extensions tsx srccodemod 依次处理:导入更新(React 导入迁往@apollo/client/react)→ 类型迁移(迁往新的命名空间位置)→ Link 更新(创建函数转类)→ 被移除导出的处理(迁往@apollo/client/v4-migration)。
关键的破坏性变更(对应仓库 src/core/ApolloClient.ts 的构造函数)包括:
- 安装:
rxjs成为 peer dependency,安装命令为npm install @apollo/client graphql rxjs; - 构造函数:
link变为必填(不再隐式创建HttpLink);uri/headers/credentials移除,改用HttpLink;name/version移入clientAwareness;resolvers移入LocalState构造器;connectToDevTools换成devtools.enabled;disableNetworkFetches更名为prioritizeCacheValues; - 类型系统:移除
TContext、TCacheShape泛型,类型迁入命名空间,自定义 context 改走模块增强; - Observable:从
zen-observable迁移到 RxJS,转换必须调用.pipe(),使用 RxJS 操作符; - 测试:
MockedProvider默认带真实延迟(20–50ms);移除createMockClient,改用MockLink; - 性能与构建:不再为现代特性降级转译、不内置 polyfill、开发模式通过 export conditions 控制、完善的
exports字段与 source maps; - 移除的导出:React render-prop 组件(
@apollo/client/react/components)、HOC(@apollo/client/react/hoc)、@apollo/client/react/parser、@apollo/client/utilities/globals。
官方建议的升级路径:先升到 3.14 收集废弃警告 → 安装rxjs→ 运行 codemod → 更新ApolloClient初始化(显式HttpLink、按需LocalState)→ 重写错误处理 → 重点回归 SSR、错误处理与局部状态。
三、Apollo Client 4.1:增量交付与 Fragment 观察能力增强
4.1.0 在 4.0 基础上继续强化增量交付与缓存观察 API:
GraphQL17Alpha9Handler:支持graphql@17.0.0-alpha.9实现的更新版@defer增量格式(源码见 src/incremental/handlers/graphql17Alpha9.ts)。注意:只有服务器实现了新版格式时才应使用它,Apollo Router 用户应继续使用Defer20220824Handler:
import { GraphQL17Alpha9Handler } from "@apollo/client/incremental"; const client = new ApolloClient({ incrementalHandler: new GraphQL17Alpha9Handler(), });该处理器会把accept头更新为multipart/mixed;incrementalSpec=v0.2以请求最新格式。
@stream支持:Defer20220824Handler与GraphQL17Alpha2Handler均可处理@stream指令;默认只在最后一个 chunk 时截断@stream数组,且仅当结果携带 stream 信息时才使用默认流式 merge 函数。Fragment 观察增强(
useFragment/useSuspenseFragment/client.watchFragment):from支持数组,一次观察多个实体(data按索引对应from数组);from: null时结果固定为{ data: null, dataState: "complete", complete: true };- 返回的 Observable 新增
getCurrentResult()方法; - 相同 fragment、变量与标识符的 watch 会去重,并尽量复用已有 Observable 以提升性能。
缓存写入增强:
cache.write/cache.writeQuery/client.writeQuery新增extensions选项,使 GraphQL 操作的extensions在 merge 函数中可用(源码参见 src/cache 相关实现);InMemoryCache不再过滤read函数显式返回的undefined数组项;readFragment/watchFragment/updateFragment全面支持from选项。@client字段行为修正:定义了read函数时,不再把回退值强制设为null,而是以existing === undefined调用read;LocalState在无 resolver 且缓存无法解析@client字段(resolvesClientField返回false)时才警告并设null。
四、Apollo Client 4.2:双签名类型系统与事件驱动 Refetch
4.2.0 是本仓库当前版本主线中最具革新意义的一次次要版本,包含两个重量级特性。
4.1(4.2) classic 与 modern 双签名(Signature Styles)
4.2 为方法与 hooks 引入两种签名风格:
- Classic signatures(默认):与 4.2 之前完全一致,保持向后兼容,支持手写 TypeScript 泛型(如
useSuspenseQuery<MyData>(...))。但官方明确建议改用TypedDocumentNode让类型自动推断; - Modern signatures:自动把声明的
defaultOptions纳入返回类型,类型更准确;从文档节点推断类型,不支持手动传泛型参数(传入会产生类型错误)。
方法/钩子会在DeclareDefaultOptions中声明了任意非可选属性时,全局自动切换到 modern 签名:
// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { namespace ApolloClient { namespace DeclareDefaultOptions { interface WatchQuery { errorPolicy: "all"; // 非可选 → 自动激活 modern 签名 } } } }也可以不声明defaultOptions、直接手动切换到 modern 签名;或声明后手动切回 classic 用于迁移(不推荐长期使用,因为会导致类型与运行时不一致):
// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { export interface TypeOverrides { signatureStyle: "modern"; // 或 "classic" } }4.2 defaultOptions 类型安全:必须全局登记
从 4.2 开始,某些defaultOptions类型必须全局登记,否则ApolloClient构造会直接报 TypeScript 错误。可登记的键包括:WatchQuery的errorPolicy与returnPartialData、Query与Mutate的errorPolicy。
// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { namespace ApolloClient { namespace DeclareDefaultOptions { interface WatchQuery { errorPolicy: "all"; } interface Query { errorPolicy: "all"; } interface Mutate { errorPolicy: "all"; } } } }登记后,useSuspenseQuery(MY_QUERY)的data类型会从TData自动变为TData | undefined,与"发生错误时data可能为undefined"的运行时行为保持一致;单次调用显式传errorPolicy: "none"又会把data收窄回TData。
若同一应用存在多个defaultOptions冲突的客户端实例,可用联合类型放宽约束:
declare module "@apollo/client" { export namespace ApolloClient { export namespace DeclareDefaultOptions { interface WatchQuery { errorPolicy?: "none" | "all" | "ignore"; returnPartialData?: boolean; } } } }代价是返回类型会变得更泛化;把属性设为可选(errorPolicy?:)等价于在联合类型中加入 TypeScript 默认值"none"。仅使用部分取值时,建议声明精确的必选联合(如errorPolicy: "all" | "ignore")以保持签名收窄。
4.3 类型安全扩展到 mutate 与 preloadQuery
client.mutate/useMutation:errorPolicy现在流入结果类型(对应仓库 src/core/types.ts 中ApolloClient.MutateResult):"none"→{ data: TData; error?: never };"all"→{ data: TData | undefined; error?: ErrorLike };"ignore"→{ data: TData | undefined; error?: never }。声明DeclareDefaultOptions.Mutate.errorPolicy后,hooks 与方法返回类型随之收窄;单次调用显式传errorPolicy可覆盖默认值。preloadQuery(来自createQueryPreloader):DeclareDefaultOptions.WatchQuery的默认值会正确作用于PreloadedQueryRef的 data states,例如声明errorPolicy: "all"后preloadQuery(QUERY)返回PreloadedQueryRef<TData, TVariables, "complete" | "streaming" | "empty">。
4.4 事件驱动自动 Refetch(RefetchEventManager)
4.2 通过RefetchEventManager类实现基于事件的自动 refetch,如窗口聚焦(window focus)与网络重连。核心实现见 src/core/RefetchEventManager.ts,内置事件源为 src/core/refetchSources/windowFocusSource.ts 与 src/core/refetchSources/onlineSource.ts。
事件 refetch 完全按需启用:构造RefetchEventManager并传入ApolloClient构造函数即可激活事件监听:
import { ApolloClient, InMemoryCache, RefetchEventManager, windowFocusSource, onlineSource, } from "@apollo/client"; const client = new ApolloClient({ link, cache: new InMemoryCache(), refetchEventManager: new RefetchEventManager({ sources: { // 窗口聚焦时 refetch windowFocus: windowFocusSource, // 用户恢复在线时 refetch online: onlineSource, }, }), });默认情况下所有活跃查询都会在事件触发时 refetch,查询可逐事件关闭或整体关闭:
// 窗口聚焦时不 refetch,但保留 online useQuery(QUERY, { refetchOn: { windowFocus: false }, }); // 关闭该查询的所有事件驱动 refetch useQuery(OTHER_QUERY, { refetchOn: false, }); // 无条件启用所有事件 useQuery(LIVE_DASHBOARD, { refetchOn: true, }); // 事件触发时动态决定是否 refetch useQuery(LIVE_DASHBOARD, { refetchOn: ({ source, payload }) => { if (source === "windowFocus") { return someCondition(payload); } return true; }, });也可以按事件配置动态回调:
useQuery(LIVE_DASHBOARD, { refetchOn: { windowFocus: ({ payload }) => someCondition(payload), }, });per-query 按需启用模式:在defaultOptions.watchQuery.refetchOn设为false(或true、回调函数),再逐查询开启。当defaultOptions与 per-query 的refetchOn同时提供时,两者会合并——defaultOptions的值作用于 per-query 对象未显式配置的事件:
const client = new ApolloClient({ link, cache, refetchEventManager: new RefetchEventManager({ sources: { windowFocus: windowFocusSource }, }), defaultOptions: { watchQuery: { refetchOn: false }, }, }); // 只有该查询会在窗口聚焦时 refetch useQuery(DASHBOARD_QUERY, { refetchOn: { windowFocus: true } });自定义事件:通过 TypeScript 模块增强注册事件名与 payload 类型,再提供返回 Observable 的 source 函数(发出值即事件 payload):
import { Observable } from "@apollo/client"; import { filter } from "rxjs"; import { AppState, AppStateStatus, Platform } from "react-native"; declare module "@apollo/client" { interface RefetchEvents { reactNativeAppStatus: AppStateStatus; } } const refetchEventManager = new RefetchEventManager({ sources: { reactNativeAppStatus: () => { return new Observable((observer) => { const subscription = AppState.addEventListener("change", (status) => { observer.next(status); }); return () => subscription.remove(); }).pipe( filter((status) => Platform.OS !== "web" && status === "active") ); }, }, });手动触发:调用emit(eventName, payload)即可命令式触发事件 refetch:
refetchEventManager.emit("reactNativeAppStatus", "active");无源事件(Sourceless events):某个事件没有自动检测逻辑、只支持命令式emit时,source 可声明为true,并将事件类型声明为void以省略 payload 参数:
declare module "@apollo/client" { interface RefetchEvents { userTriggered: void; } } const refetchEventManager = new RefetchEventManager({ sources: { userTriggered: true }, }); refetchEventManager.emit("userTriggered");注意:对未注册 source 的事件调用emit会输出警告并成为 no-op。
自定义 handler:事件触发时,默认 handler 调用client.refetchQueries({ include: "active" }),再按各查询的refetchOn设置过滤。可针对某事件覆盖 handler,例如把所有查询(含standby)都纳入 refetch:
const refetchEventManager = new RefetchEventManager({ // ... handlers: { userTriggered: ({ client, source, payload, matchesRefetchOn }) => { return client.refetchQueries({ include: "all", onQueryUpdated: (observableQuery) => { return matchesRefetchOn(observableQuery); }, }); }, }, });handler 必须返回RefetchQueriesResult或void(返回void表示该事件跳过 refetch)。此外 4.2.0 还支持通过构造选项defaultHandler或实例方法setDefaultEventHandler覆盖默认handler(未配置 per-source handler 时兜底运行),见 src/core/RefetchEventManager.ts 中Options.defaultHandler的注释说明。
4.5 4.2.x 补丁版本要点
- 4.2.12:修复多字节 UTF-8 字符被 multipart 响应块切分后丢失的问题(PR #13400);
- 4.2.11:新增开发模式警告——网络结果写入缓存后,回读得到部分结果时提示,通常指向
merge/read函数未修复缓存缺失字段; - 4.2.10:修复
optimisticResponse导致client.mutate返回类型意外放宽、常量类型变量导致returnPartialData/errorPolicy类型被放宽、modern 签名下未知选项未被 TypeScript 拦截等问题;refetch/fetchMore/useLazyQuery的execute返回类型随errorPolicy收窄; - 4.2.9:修复变量显式传
undefined时默认值未在缓存读取中生效、@export查询对导出变量键控字段的缓存更新无响应的问题; - 4.2.8:
connectToDevtools的setTimeout不再在非 Chrome/Firefox 环境触发,消除测试抖动; - 4.2.6:缓存写入提速——仅在结果携带 stream 信息时才做
@stream检测,避免对每个写入字段做完整 ASTvisit;嵌套字段上的@stream不再被视为流式字段本身; - 4.2.5:从公共入口导出
KeyArgsFunction与RelayFieldPolicy类型; - 4.2.4:修复
client.readFragment/client.readQuery忽略 options 对象中optimistic选项的问题; - 4.2.3:
graphqlv17 被接纳为合法 peer dependency(仓库 patches 目录中亦包含graphql-17-alpha9的补丁佐证); - 4.2.2:refetch 时若掩码(masked)数据与上一结果深度相等,则保持引用相等;
- 4.2.1:修复
useLazyQuery在渲染间隔中修改pollInterval不生效的问题。
五、3.x 至 2.x:历史演进一瞥
4.0 之前的历史版本同样记录了大量关键演进,阅读时可按需回溯:
- 3.14:作为 4.0 前的过渡版本,集中输出废弃警告,是官方升级路径的第 1 步;
- 3.8:引入
useFragment、useSuspenseFragment、useBackgroundQuery等 hooks 与新 SSR 能力; - 3.6–3.7:缓存、
fetchMore、网络状态与 Link 链路的持续修复; - 3.0:
InMemoryCache归一化缓存与字段策略(field policies)成为主流; - 2.x:记录了
ObservableQuery<TData, TVariables>泛型化、QueryOptions与WatchQueryOptions分离、fetch policy 与 SSR 修复等历史沿革。
六、结语:如何利用这份 CHANGELOG 指导开发
- 升级前:先读 4.0.0 Release Notes 的 Breaking Changes 清单,走完"3.14 → 装 rxjs → codemod → 改初始化 → 改错误处理 → 回归测试"的标准路径;
- 接入 4.2 新特性:事件驱动 Refetch 从 src/core/RefetchEventManager.ts 与 src/core/refetchSources 出发即可快速上手;类型安全能力则对照 src/core/types.ts 与
DeclareDefaultOptions的模块增强模式; - 排查存量问题:按版本号在 CHANGELOG.md 中检索对应修复项,通常能快速定位到源码与测试(如 src/core/tests),做到"改了什么、为什么改"有据可查。
从 4.0 的架构重构到 4.2 的类型系统现代化与事件驱动 refetch,Apollo Client 的演进主线始终围绕"更小的包、更准的类型、更灵活的框架适配"展开——这份 CHANGELOG 既是升级手册,也是理解客户端缓存与状态管理的绝佳源码级教材。
【免费下载链接】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
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考