重構(gòu)到 4.2 類型安全與事件驅(qū)動 Refetch)
Apollo Client 版本演進全解讀從 4.0 架構(gòu)重構(gòu)到 4.2 類型安全與事件驅(qū)動 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這篇技術(shù)指南以倉庫根目錄的 CHANGELOG.md覆蓋 Apollo Client 2.3.2 至 4.2.12 的全部版本記錄為核心脈絡(luò)系統(tǒng)梳理 Apollo Client 4.x 系列帶來的架構(gòu)級變革框架無關(guān)核心、統(tǒng)一錯誤處理、dataState結(jié)果狀態(tài)、可插拔增量交付、classic/modern 雙簽名類型系統(tǒng)以及 4.2 全新引入的事件驅(qū)動自動 RefetchRefetchEventManager。讀完本文你將理解每個版本為什么改、改了什么、如何遷移并能直接對照倉庫源碼src/core、src/errors、src/incremental、src/local-state等驗證底層實現(xiàn)。一、CHANGELOG 覆蓋范圍與閱讀方法當(dāng)前倉庫的 CHANGELOG.md 是一份累計超過 1 萬行的完整版本歷史覆蓋從 2018 年的2.3.2到當(dāng)前最新的4.2.124.2.x 系列最新的補丁與次要版本當(dāng)前頭部包含類型系統(tǒng)現(xiàn)代化與事件驅(qū)動 Refetch 兩大主線4.0.0 大版本一次完整的架構(gòu)重構(gòu)文內(nèi)含獨立的Release Notes專題章節(jié)見 CHANGELOG.md 4.0.0 一節(jié)是理解現(xiàn)代 Apollo Client 設(shè)計哲學(xué)的核心材料3.x / 2.x 系列早期歷史記錄了大量緩存、ObservableQuery、fetch policy 相關(guān)的修復(fù)對排查歷史問題仍有參考價值。版本條目統(tǒng)一采用 Changesets 格式每個條目包含 PR 鏈接、提交哈希、貢獻者與變更說明并用### Minor Changes新特性與### Patch Changes修復(fù)分類。下文聚焦最有價值的 4.x 主線展開。二、Apollo Client 4.0一次徹底的架構(gòu)重構(gòu)4.0.0 的 Release Notes 明確將其定位為更現(xiàn)代、更高效、更類型安全的 GraphQL 客戶端體驗核心圍繞開發(fā)者體驗、包體積優(yōu)化與框架靈活性三個方向。2.1 框架無關(guān)的核心Framework-Agnostic Core4.0 將 React 功能與核心庫分離React 相關(guān)導(dǎo)出統(tǒng)一收歸apollo/client/react核心不再依賴 React。這意味著你可以在任意 JavaScript 框架中使用 Apollo Client 而不會引入 React 依賴。倉庫中 src/react/index.ts 與 src/core/ApolloClient.ts 的目錄劃分正是這一架構(gòu)的落地體現(xiàn)。2.2 更小的包體積局部狀態(tài)按需引入client指令能力改為通過LocalState類按需啟用源碼見 src/local-state/LocalState.ts不使用局部狀態(tài)時不會被打包現(xiàn)代構(gòu)建目標(biāo)轉(zhuǎn)譯目標(biāo)為since 2023, node 20, not dead充分利用現(xiàn)代 JavaScript 特性更優(yōu)的 Tree-Shakingpackage.json中規(guī)范的exports字段讓死代碼消除更徹底。2.3 統(tǒng)一錯誤處理Unified Error Handling4.0 移除了ApolloError改用一組更細粒度的錯誤類并統(tǒng)一收斂到單一的error屬性錯誤類語義CombinedGraphQLErrors服務(wù)器返回的 GraphQL 錯誤源碼見 src/errors/CombinedGraphQLErrors.tsServerError非 GraphQL 的服務(wù)器錯誤ServerParseError服務(wù)器響應(yīng)解析錯誤UnconventionalError非 Error 對象被 throw 時的包裝LinkError鏈路Link拋出的錯誤通過.is()判定這些類都提供靜態(tài).is()方法用于類型收窄網(wǎng)絡(luò)錯誤也遵循errorPolicy設(shè)置外部錯誤則原樣透傳不再包裝。遷移示例// 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清晰標(biāo)識查詢結(jié)果的完整性是 4.0 類型系統(tǒng)的基石之一empty無數(shù)據(jù)data為undefinedpartialreturnPartialData為true時從緩存讀到的部分數(shù)據(jù)streamingdefer查詢?nèi)栽诹魇椒祷刂械牟煌暾麛?shù)據(jù)complete完全滿足的查詢結(jié)果。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/stream4.0 將增量交付設(shè)計為可配置、面向未來的機制通過incrementalHandler注入。倉庫中的導(dǎo)出見 src/incremental/index.tsimport { Defer20220824Handler } from apollo/client/incremental; const client new ApolloClient({ // ... incrementalHandler: new Defer20220824Handler(), });NotImplementedHandler默認處理器一旦使用defer直接拋出異常Defer20220824Handler支持 Apollo Router 的增量格式同時以GraphQL17Alpha2Handler別名導(dǎo)出。2.6 局部狀態(tài)管理LocalState 類client字段解析改為顯式啟用import { LocalState } from apollo/client/local-state; const client new ApolloClient({ cache, localState: new LocalState({ resolvers: { Query: { myField: () Hello World, }, }, }), });同時 resolver 的context結(jié)構(gòu)發(fā)生變化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 側(cè)改進useLazyQuery重構(gòu)不再接受variables/context選項改由execute接收execute只接受variables與context禁止在渲染期間或 SSR 中調(diào)用新查詢啟動時自動取消在途查詢useMutation移除ignoreResults選項如需 fire-and-forget 直接使用client.mutateuseQuerynotifyOnNetworkStatusChange默認改為true移除廢棄的onCompleted/onError回調(diào)新 SSR APIprerenderStatic取代舊的 SSR 函數(shù)支持 React 19 的 prerender APIimport { prerenderStatic } from apollo/client/react/ssr; const html await prerenderStatic(App /, { client, });React Compiler 支持提供apollo/client/react/compiled入口見 src/react/index.ts 中 compiled 運行時內(nèi)置 React 17 兼容的運行時 polyfill。2.8 Link 體系演進全部類化創(chuàng)建函數(shù)creator function統(tǒng)一遷移為類// 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()判定對應(yīng)倉庫 src/link/error/index.ts。2.9 遷移工具與破壞性變更清單4.0 提供自動化 codemod# 基礎(chǔ)用法 npx apollo/client-codemod-migrate-3-to-4 src # TypeScript 項目分開執(zhí)行 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 依次處理導(dǎo)入更新React 導(dǎo)入遷往apollo/client/react→ 類型遷移遷往新的命名空間位置→ Link 更新創(chuàng)建函數(shù)轉(zhuǎn)類→ 被移除導(dǎo)出的處理遷往apollo/client/v4-migration。關(guān)鍵的破壞性變更對應(yīng)倉庫 src/core/ApolloClient.ts 的構(gòu)造函數(shù)包括安裝rxjs成為 peer dependency安裝命令為npm install apollo/client graphql rxjs構(gòu)造函數(shù)link變?yōu)楸靥畈辉匐[式創(chuàng)建HttpLinkuri/headers/credentials移除改用HttpLinkname/version移入clientAwarenessresolvers移入LocalState構(gòu)造器connectToDevTools換成devtools.enableddisableNetworkFetches更名為prioritizeCacheValues類型系統(tǒng)移除TContext、TCacheShape泛型類型遷入命名空間自定義 context 改走模塊增強Observable從zen-observable遷移到 RxJS轉(zhuǎn)換必須調(diào)用.pipe()使用 RxJS 操作符測試MockedProvider默認帶真實延遲20–50ms移除createMockClient改用MockLink性能與構(gòu)建不再為現(xiàn)代特性降級轉(zhuǎn)譯、不內(nèi)置 polyfill、開發(fā)模式通過 export conditions 控制、完善的exports字段與 source maps移除的導(dǎo)出React render-prop 組件apollo/client/react/components、HOCapollo/client/react/hoc、apollo/client/react/parser、apollo/client/utilities/globals。官方建議的升級路徑先升到 3.14 收集廢棄警告 → 安裝rxjs→ 運行 codemod → 更新ApolloClient初始化顯式HttpLink、按需LocalState→ 重寫錯誤處理 → 重點回歸 SSR、錯誤處理與局部狀態(tài)。三、Apollo Client 4.1增量交付與 Fragment 觀察能力增強4.1.0 在 4.0 基礎(chǔ)上繼續(xù)強化增量交付與緩存觀察 APIGraphQL17Alpha9Handler支持graphql17.0.0-alpha.9實現(xiàn)的更新版defer增量格式源碼見 src/incremental/handlers/graphql17Alpha9.ts。注意只有服務(wù)器實現(xiàn)了新版格式時才應(yīng)使用它Apollo Router 用戶應(yīng)繼續(xù)使用Defer20220824Handlerimport { GraphQL17Alpha9Handler } from apollo/client/incremental; const client new ApolloClient({ incrementalHandler: new GraphQL17Alpha9Handler(), });該處理器會把accept頭更新為multipart/mixed;incrementalSpecv0.2以請求最新格式。stream支持Defer20220824Handler與GraphQL17Alpha2Handler均可處理stream指令默認只在最后一個 chunk 時截斷stream數(shù)組且僅當(dāng)結(jié)果攜帶 stream 信息時才使用默認流式 merge 函數(shù)。Fragment 觀察增強useFragment/useSuspenseFragment/client.watchFragmentfrom支持數(shù)組一次觀察多個實體data按索引對應(yīng)from數(shù)組from: null時結(jié)果固定為{ data: null, dataState: complete, complete: true }返回的 Observable 新增getCurrentResult()方法相同 fragment、變量與標(biāo)識符的 watch 會去重并盡量復(fù)用已有 Observable 以提升性能。緩存寫入增強cache.write/cache.writeQuery/client.writeQuery新增extensions選項使 GraphQL 操作的extensions在 merge 函數(shù)中可用源碼參見 src/cache 相關(guān)實現(xiàn)InMemoryCache不再過濾read函數(shù)顯式返回的undefined數(shù)組項readFragment/watchFragment/updateFragment全面支持from選項。client字段行為修正定義了read函數(shù)時不再把回退值強制設(shè)為null而是以existing undefined調(diào)用readLocalState在無 resolver 且緩存無法解析client字段resolvesClientField返回false時才警告并設(shè)null。四、Apollo Client 4.2雙簽名類型系統(tǒng)與事件驅(qū)動 Refetch4.2.0 是本倉庫當(dāng)前版本主線中最具革新意義的一次次要版本包含兩個重量級特性。4.14.2 classic 與 modern 雙簽名Signature Styles4.2 為方法與 hooks 引入兩種簽名風(fēng)格Classic signatures默認與 4.2 之前完全一致保持向后兼容支持手寫 TypeScript 泛型如useSuspenseQueryMyData(...)。但官方明確建議改用TypedDocumentNode讓類型自動推斷Modern signatures自動把聲明的defaultOptions納入返回類型類型更準(zhǔn)確從文檔節(jié)點推斷類型不支持手動傳泛型參數(shù)傳入會產(chǎn)生類型錯誤。方法/鉤子會在DeclareDefaultOptions中聲明了任意非可選屬性時全局自動切換到 modern 簽名// apollo.d.ts import apollo/client; declare module apollo/client { namespace ApolloClient { namespace DeclareDefaultOptions { interface WatchQuery { errorPolicy: all; // 非可選 → 自動激活 modern 簽名 } } } }也可以不聲明defaultOptions、直接手動切換到 modern 簽名或聲明后手動切回 classic 用于遷移不推薦長期使用因為會導(dǎo)致類型與運行時不一致// apollo.d.ts import apollo/client; declare module apollo/client { export interface TypeOverrides { signatureStyle: modern; // 或 classic } }4.2 defaultOptions 類型安全必須全局登記從 4.2 開始某些defaultOptions類型必須全局登記否則ApolloClient構(gòu)造會直接報 TypeScript 錯誤??傻怯浀逆I包括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自動變?yōu)門Data | undefined與發(fā)生錯誤時data可能為undefined的運行時行為保持一致單次調(diào)用顯式傳errorPolicy: none又會把data收窄回TData。若同一應(yīng)用存在多個defaultOptions沖突的客戶端實例可用聯(lián)合類型放寬約束declare module apollo/client { export namespace ApolloClient { export namespace DeclareDefaultOptions { interface WatchQuery { errorPolicy?: none | all | ignore; returnPartialData?: boolean; } } } }代價是返回類型會變得更泛化把屬性設(shè)為可選errorPolicy?:等價于在聯(lián)合類型中加入 TypeScript 默認值none。僅使用部分取值時建議聲明精確的必選聯(lián)合如errorPolicy: all | ignore以保持簽名收窄。4.3 類型安全擴展到 mutate 與 preloadQueryclient.mutate/useMutationerrorPolicy現(xiàn)在流入結(jié)果類型對應(yīng)倉庫 src/core/types.ts 中ApolloClient.MutateResultnone→{ data: TData; error?: never }all→{ data: TData | undefined; error?: ErrorLike }ignore→{ data: TData | undefined; error?: never }。聲明DeclareDefaultOptions.Mutate.errorPolicy后hooks 與方法返回類型隨之收窄單次調(diào)用顯式傳errorPolicy可覆蓋默認值。preloadQuery來自createQueryPreloaderDeclareDefaultOptions.WatchQuery的默認值會正確作用于PreloadedQueryRef的 data states例如聲明errorPolicy: all后preloadQuery(QUERY)返回PreloadedQueryRefTData, TVariables, complete | streaming | empty。4.4 事件驅(qū)動自動 RefetchRefetchEventManager4.2 通過RefetchEventManager類實現(xiàn)基于事件的自動 refetch如窗口聚焦window focus與網(wǎng)絡(luò)重連。核心實現(xiàn)見 src/core/RefetchEventManager.ts內(nèi)置事件源為 src/core/refetchSources/windowFocusSource.ts 與 src/core/refetchSources/onlineSource.ts。事件 refetch 完全按需啟用構(gòu)造RefetchEventManager并傳入ApolloClient構(gòu)造函數(shù)即可激活事件監(jiān)聽import { ApolloClient, InMemoryCache, RefetchEventManager, windowFocusSource, onlineSource, } from apollo/client; const client new ApolloClient({ link, cache: new InMemoryCache(), refetchEventManager: new RefetchEventManager({ sources: { // 窗口聚焦時 refetch windowFocus: windowFocusSource, // 用戶恢復(fù)在線時 refetch online: onlineSource, }, }), });默認情況下所有活躍查詢都會在事件觸發(fā)時 refetch查詢可逐事件關(guān)閉或整體關(guān)閉// 窗口聚焦時不 refetch但保留 online useQuery(QUERY, { refetchOn: { windowFocus: false }, }); // 關(guān)閉該查詢的所有事件驅(qū)動 refetch useQuery(OTHER_QUERY, { refetchOn: false, }); // 無條件啟用所有事件 useQuery(LIVE_DASHBOARD, { refetchOn: true, }); // 事件觸發(fā)時動態(tài)決定是否 refetch useQuery(LIVE_DASHBOARD, { refetchOn: ({ source, payload }) { if (source windowFocus) { return someCondition(payload); } return true; }, });也可以按事件配置動態(tài)回調(diào)useQuery(LIVE_DASHBOARD, { refetchOn: { windowFocus: ({ payload }) someCondition(payload), }, });per-query 按需啟用模式在defaultOptions.watchQuery.refetchOn設(shè)為false或true、回調(diào)函數(shù)再逐查詢開啟。當(dāng)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 函數(shù)發(fā)出值即事件 payloadimport { 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) ); }, }, });手動觸發(fā)調(diào)用emit(eventName, payload)即可命令式觸發(fā)事件 refetchrefetchEventManager.emit(reactNativeAppStatus, active);無源事件Sourceless events某個事件沒有自動檢測邏輯、只支持命令式emit時source 可聲明為true并將事件類型聲明為void以省略 payload 參數(shù)declare module apollo/client { interface RefetchEvents { userTriggered: void; } } const refetchEventManager new RefetchEventManager({ sources: { userTriggered: true }, }); refetchEventManager.emit(userTriggered);注意對未注冊 source 的事件調(diào)用emit會輸出警告并成為 no-op。自定義 handler事件觸發(fā)時默認 handler 調(diào)用client.refetchQueries({ include: active })再按各查詢的refetchOn設(shè)置過濾。可針對某事件覆蓋 handler例如把所有查詢含standby都納入 refetchconst 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 還支持通過構(gòu)造選項defaultHandler或?qū)嵗椒╯etDefaultEventHandler覆蓋默認handler未配置 per-source handler 時兜底運行見 src/core/RefetchEventManager.ts 中Options.defaultHandler的注釋說明。4.5 4.2.x 補丁版本要點4.2.12修復(fù)多字節(jié) UTF-8 字符被 multipart 響應(yīng)塊切分后丟失的問題PR #134004.2.11新增開發(fā)模式警告——網(wǎng)絡(luò)結(jié)果寫入緩存后回讀得到部分結(jié)果時提示通常指向merge/read函數(shù)未修復(fù)緩存缺失字段4.2.10修復(fù)optimisticResponse導(dǎo)致client.mutate返回類型意外放寬、常量類型變量導(dǎo)致returnPartialData/errorPolicy類型被放寬、modern 簽名下未知選項未被 TypeScript 攔截等問題refetch/fetchMore/useLazyQuery的execute返回類型隨errorPolicy收窄4.2.9修復(fù)變量顯式傳undefined時默認值未在緩存讀取中生效、export查詢對導(dǎo)出變量鍵控字段的緩存更新無響應(yīng)的問題4.2.8connectToDevtools的setTimeout不再在非 Chrome/Firefox 環(huán)境觸發(fā)消除測試抖動4.2.6緩存寫入提速——僅在結(jié)果攜帶 stream 信息時才做stream檢測避免對每個寫入字段做完整 ASTvisit嵌套字段上的stream不再被視為流式字段本身4.2.5從公共入口導(dǎo)出KeyArgsFunction與RelayFieldPolicy類型4.2.4修復(fù)client.readFragment/client.readQuery忽略 options 對象中optimistic選項的問題4.2.3graphqlv17 被接納為合法 peer dependency倉庫 patches 目錄中亦包含graphql-17-alpha9的補丁佐證4.2.2refetch 時若掩碼masked數(shù)據(jù)與上一結(jié)果深度相等則保持引用相等4.2.1修復(fù)useLazyQuery在渲染間隔中修改pollInterval不生效的問題。五、3.x 至 2.x歷史演進一瞥4.0 之前的歷史版本同樣記錄了大量關(guān)鍵演進閱讀時可按需回溯3.14作為 4.0 前的過渡版本集中輸出廢棄警告是官方升級路徑的第 1 步3.8引入useFragment、useSuspenseFragment、useBackgroundQuery等 hooks 與新 SSR 能力3.6–3.7緩存、fetchMore、網(wǎng)絡(luò)狀態(tài)與 Link 鏈路的持續(xù)修復(fù)3.0InMemoryCache歸一化緩存與字段策略field policies成為主流2.x記錄了ObservableQueryTData, TVariables泛型化、QueryOptions與WatchQueryOptions分離、fetch policy 與 SSR 修復(fù)等歷史沿革。六、結(jié)語如何利用這份 CHANGELOG 指導(dǎo)開發(fā)升級前先讀 4.0.0 Release Notes 的 Breaking Changes 清單走完3.14 → 裝 rxjs → codemod → 改初始化 → 改錯誤處理 → 回歸測試的標(biāo)準(zhǔn)路徑接入 4.2 新特性事件驅(qū)動 Refetch 從 src/core/RefetchEventManager.ts 與 src/core/refetchSources 出發(fā)即可快速上手類型安全能力則對照 src/core/types.ts 與DeclareDefaultOptions的模塊增強模式排查存量問題按版本號在 CHANGELOG.md 中檢索對應(yīng)修復(fù)項通常能快速定位到源碼與測試如 src/core/tests做到改了什么、為什么改有據(jù)可查。從 4.0 的架構(gòu)重構(gòu)到 4.2 的類型系統(tǒng)現(xiàn)代化與事件驅(qū)動 refetchApollo Client 的演進主線始終圍繞更小的包、更準(zhǔn)的類型、更靈活的框架適配展開——這份 CHANGELOG 既是升級手冊也是理解客戶端緩存與狀態(tài)管理的絕佳源碼級教材?!久赓M下載鏈接】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創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考