據(jù)預(yù)取與查詢引用管理)
前端開發(fā)工具【免費(fèi)下載鏈接】relayRelay is a JavaScript framework for building>項(xiàng)目地址https://gitcode.com/gh_mirrors/relay29/relay點(diǎn)擊查看免費(fèi)下載loadQuery是 Relay 中用于實(shí)現(xiàn) render-as-you-fetch邊渲染邊取數(shù)模式的核心命令式 API它在 React 渲染之外例如路由導(dǎo)航、點(diǎn)擊事件主動發(fā)起 GraphQL 查詢請求返回一個(gè)由 Relay Store 保留retain的 query reference再交由usePreloadedQuery在渲染階段消費(fèi)。本文以 React Relay v19 官方 API 文檔為主體結(jié)合倉庫源碼逐層拆解loadQuery的參數(shù)語義、返回值的生命周期、與useQueryLoader/usePreloadedQuery的協(xié)作方式以及底層去重與保留機(jī)制幫助你寫出既早發(fā)起請求又不泄漏 Store 數(shù)據(jù)的高質(zhì)量代碼。loadQuery是什么設(shè)計(jì)初衷與適用場景l(fā)oadQuery是react-relay導(dǎo)出的頂層函數(shù)設(shè)計(jì)目標(biāo)是與usePreloadedQuery()搭配實(shí)現(xiàn)render-as-you-fetch。與useLazyLoadQuery在組件掛載/渲染時(shí)才觸發(fā)請求不同loadQuery允許你在 React 的渲染階段之外如路由跳轉(zhuǎn)、用戶點(diǎn)擊、EntryPoint 預(yù)加載盡早開始數(shù)據(jù)請求讓網(wǎng)絡(luò)請求與 UI 渲染并行進(jìn)行從而縮短用戶可感知的等待時(shí)間。其核心契約包含三點(diǎn)主動拉取并寫入傳入查詢后立即取數(shù)待查詢與數(shù)據(jù)都可用時(shí)把數(shù)據(jù)寫入 Relay Store。保留retain數(shù)據(jù)返回的 query reference 會被 Relay Store 保留防止數(shù)據(jù)被垃圾回收。需要顯式釋放如果不再引用該 query reference 而沒有調(diào)用.dispose()數(shù)據(jù)會泄漏在 Relay Store 中。因此官方文檔明確建議能使用useQueryLoader時(shí)優(yōu)先使用它因?yàn)樵?Hook 會替你妥善調(diào)用 dispose。從源碼注釋看loadQuery.jsv19 中l(wèi)oadQuery與舊 APIpreloadQuery_DEPRECATED的行為差異已被明確強(qiáng)調(diào)loadQuery一旦拿到查詢與數(shù)據(jù)就會把結(jié)果寫入 Store而preloadQuery_DEPRECATED只有在查詢被傳給usePreloadedQuery之后才寫 Store。這一差異在下面的“Behavior”小節(jié)會展開說明。基本用法與完整代碼示例loadQuery簽名如下參見 EntryPointTypes.flow.js 中LoadQueryOptions與 loadQuery.js 的實(shí)現(xiàn)loadQuery( environment: IEnvironment, preloadableRequest: GraphQLTaggedNode | PreloadableConcreteRequest, variables: Variables, options?: ?LoadQueryOptions, environmentProviderOptions?: ?EnvironmentProviderOptions, ): PreloadedQuery官方文檔給出的最小示例const MyEnvironment require(MyEnvironment); const {loadQuery} require(react-relay); const query graphql query AppQuery($id: ID!) { user(id: $id) { name } } ; // 注意通常不應(yīng)在模塊頂層調(diào)用 loadQuery。 // 而應(yīng)在事件回調(diào)中調(diào)用如路由導(dǎo)航、點(diǎn)擊等。 const queryReference loadQuery( MyEnvironment, query, {id: 4}, {fetchPolicy: store-or-network}, ); // 稍后把 queryReference 傳給 usePreloadedQuery() // 注意query reference 應(yīng)當(dāng)調(diào)用 .dispose()本例中省略了這一步。更完整的 render-as-you-fetch 用法loadQuery通常不會單獨(dú)使用而是與usePreloadedQuery或useQueryLoader配合形成“事件觸發(fā)預(yù)加載 → 渲染階段讀取”的完整鏈路。完整示例參見 usePreloadedQuery 文檔 與 useQueryLoader 文檔import type {AppQueryType} from AppQueryType.graphql; const React require(React); const {graphql, useQueryLoader, usePreloadedQuery} require(react-relay); const AppQuery graphql query AppQuery($id: ID!) { user(id: $id) { name } } ; type Props { initialQueryRef: PreloadedQueryAppQueryType, }; function NameLoader(props) { const [queryReference, loadQuery] useQueryLoader( AppQuery, props.initialQueryRef, /* 例如由 router 提供 */ ); return ( Button onClick{() loadQuery({id: 4})} disabled{queryReference ! null} Reveal your name! /Button Suspense fallbackLoading... {queryReference ! null ? NameDisplay queryReference{queryReference} / : null } /Suspense /); } function NameDisplay({ queryReference }) { const data usePreloadedQuery(AppQuery, queryReference); return h1{data.user?.name}/h1; }在這個(gè)模式中事件回調(diào)里調(diào)用loadQuery此處通過useQueryLoader返回的回調(diào)提前發(fā)起請求渲染階段usePreloadedQuery讀取 Store查詢?nèi)栽谶M(jìn)行則 suspend失敗則拋錯(cuò)成功則返回與查詢形狀一致的數(shù)據(jù)對象Flow 類型也會根據(jù) GraphQL Schema 推導(dǎo)例如{ user: ?{ name: ?string } }。參數(shù)詳解environmentRelay Environment 實(shí)例請求在該 Environment 上執(zhí)行。如果是在 React 組件內(nèi)部發(fā)起請求建議使用useRelayEnvironment()獲取當(dāng)前上下文中的 Environment而不是自行構(gòu)造。從 useQueryLoader.js 的實(shí)現(xiàn)可以看到Hook 內(nèi)部正是通過useRelayEnvironment()獲取環(huán)境并在調(diào)用loadQuery時(shí)傳入。query要取數(shù)的 GraphQL 查詢有兩種指定方式使用graphql模板字符串如graphql\query AppQuery($id: ID!) { ... }或者使用preloadable concrete request——即通過 require 形如name-of-query$Parameters.graphql的文件獲得。Relay 只有在查詢被preloadable注解時(shí)才會生成$Parameters文件。傳入 preloadable concrete request 的意義在于即使查詢的 AST 尚未加載也可以先用參數(shù)params立即發(fā)起網(wǎng)絡(luò)請求待查詢模塊加載完成后再執(zhí)行操作。在 loadQuery.js 中可以看到這條分支當(dāng)preloadableRequest.kind PreloadableConcreteRequest時(shí)若PreloadableQueryRegistry中還沒有對應(yīng)的模塊會先用params直接發(fā)起原始網(wǎng)絡(luò)請求再通過PreloadableQueryRegistry.onLoad(queryId, callback)等待模塊加載完成后補(bǔ)建OperationDescriptor并執(zhí)行操作。variables包含查詢所需變量值的對象必須與查詢內(nèi)部聲明的 GraphQL 變量匹配。缺失或類型不符的變量會導(dǎo)致請求失敗或類型錯(cuò)誤。options可選一個(gè) options 對象包含以下字段字段默認(rèn)值說明fetchPolicystore-or-network詳見下文源碼補(bǔ)充決定是否復(fù)用本地緩存、以及何時(shí)發(fā)起網(wǎng)絡(luò)請求networkCacheConfig{force: true}網(wǎng)絡(luò)層緩存配置對象fetchPolicy的三種取值store-or-network默認(rèn)會復(fù)用本地緩存數(shù)據(jù)并且僅當(dāng)查詢的某些數(shù)據(jù)缺失時(shí)才發(fā)網(wǎng)絡(luò)請求。若查詢已被完整緩存則不會發(fā)網(wǎng)絡(luò)請求。store-and-network會復(fù)用本地緩存數(shù)據(jù)并且總是發(fā)網(wǎng)絡(luò)請求無論本地緩存是否缺失數(shù)據(jù)。network-only不會復(fù)用本地緩存數(shù)據(jù)總是發(fā)網(wǎng)絡(luò)請求取數(shù)忽略 Relay 中任何本地緩存。更多細(xì)節(jié)參見 Fetch Policies 指南 與 Garbage Collection / 數(shù)據(jù)在場性指南。源碼補(bǔ)充默認(rèn) fetch policy 并非寫死。從 loadQuery.js 的getDefaultFetchPolicy可以看到默認(rèn)值取決于請求類型若request.params.metadata.live ! undefinedLive 查詢或啟用了執(zhí)行期 resolverisExecTimeResolversEnabled默認(rèn)改為store-and-networkDEFAULT_LIVE_FETCH_POLICY若查詢是僅客戶端params.id與params.text均為空且使用讀期 resolver則默認(rèn)store-only不發(fā)網(wǎng)絡(luò)請求其余情況才是store-or-network。也就是說fetchPolicy: store-or-network作為“默認(rèn)值”是針對常規(guī)服務(wù)端查詢而言Live 查詢與客戶端查詢各有其默認(rèn)策略。networkCacheConfig默認(rèn)值為{force: true}。它包含網(wǎng)絡(luò)層的緩存配置選項(xiàng)。需要注意網(wǎng)絡(luò)層可能還有一層額外的查詢響應(yīng)緩存query response cache會對相同查詢復(fù)用網(wǎng)絡(luò)響應(yīng)。若想完全繞過這層緩存這正是默認(rèn)行為傳入{force: true}即可。源碼中該默認(rèn)值被顯式強(qiáng)制見 loadQuery.jsconst networkCacheConfig { ...options?.networkCacheConfig, force: true, };也就是說即使用戶傳入自定義的networkCacheConfigforce: true也會被強(qiáng)制合并進(jìn)去確保默認(rèn)繞過網(wǎng)絡(luò)響應(yīng)緩存。這個(gè)networkCacheConfig隨后既作為網(wǎng)絡(luò)請求參數(shù)也用于創(chuàng)建OperationDescriptorcreateOperationDescriptor(concreteRequest, variables, networkCacheConfig)。environmentProviderOptions可選傳給prepareSurfaceEntryPoint.js中environmentProvider的選項(xiàng)對象見 EntryPointTypes.flow.js 中EnvironmentProviderOptions {readonly [string]: unknown, ...}。它會原樣保存在返回的 query reference 上environmentProviderOptions字段供environmentProvider.getEnvironment(options)使用。返回值Query Reference 及其生命周期loadQuery返回一個(gè)query reference內(nèi)部類型為PreloadedQuery見 EntryPointTypes.flow.js關(guān)鍵屬性如下dispose()釋放該 query reference 在 Store 中的保留retain此后其引用的數(shù)據(jù)可能被垃圾回收。這是官方文檔承諾、也最需要開發(fā)者關(guān)心的方法。官方文檔特別提醒返回值的確切格式不穩(wěn)定、極可能在未來版本中改變。強(qiáng)烈建議不要使用返回值的任何其他屬性否則升級 Relay 版本時(shí)代碼很可能損壞。正確做法是把loadQuery()的結(jié)果整體傳給usePreloadedQuery()。從源碼看 query reference 的內(nèi)部結(jié)構(gòu)雖然官方不建議直接訪問其他屬性但理解內(nèi)部結(jié)構(gòu)有助于把握生命周期。從 loadQuery.js 可以看出返回對象包含dispose()冪等地執(zhí)行“釋放數(shù)據(jù) 取消網(wǎng)絡(luò)請求”內(nèi)部調(diào)用releaseQuery()與cancelNetworkRequest()并通過isDisposed標(biāo)志防止重復(fù)執(zhí)行releaseQuery()釋放 retainretainReference.dispose()冪等isReleased標(biāo)志cancelNetworkRequest()根據(jù)是否已開始執(zhí)行網(wǎng)絡(luò)源退訂執(zhí)行訂閱或網(wǎng)絡(luò)訂閱并取消onLoad回調(diào)fetchKey每次調(diào)用loadQuery遞增的自增 key見源碼let fetchKey 100001; fetchKey用于保證每次調(diào)用生成的 query reference 都被獨(dú)立評估避免usePreloadedQuery的 Suspense 緩存錯(cuò)誤地復(fù)用上一次的查詢結(jié)果fetchPolicy、networkCacheConfig、environment、environmentProviderOptions、id、name、variables、source若發(fā)起了網(wǎng)絡(luò)請求則為一個(gè)可重放網(wǎng)絡(luò)事件的 Observable、networkError網(wǎng)絡(luò)錯(cuò)誤可被讀取、isDisposed只讀 getter等字段。這些內(nèi)部字段正是usePreloadedQuery在渲染階段恢復(fù)請求與執(zhí)行策略的數(shù)據(jù)來源參見 usePreloadedQuery.js 中對fetchKey、fetchPolicy、source、environment的使用。BehaviorloadQuery的實(shí)際行為與邊界約束取數(shù)并立即寫入 StoreloadQuery()傳入普通查詢時(shí)立即取數(shù)傳入 preloadable concrete request 時(shí)并行取數(shù)與加載查詢。一旦查詢與數(shù)據(jù)都就緒數(shù)據(jù)即被寫入 Store。這與preloadQuery_DEPRECATED不同舊 API 只有在查詢被傳給usePreloadedQuery后才寫 Store。也就是說loadQuery的“數(shù)據(jù)準(zhǔn)備”行為更徹底即使 query reference 尚未被任何組件消費(fèi)數(shù)據(jù)也已在 Store 中可用。保留與垃圾回收loadQuery返回的 query reference 會被 Relay Store保留通過environment.retain(operation)見 loadQuery.js防止其數(shù)據(jù)被垃圾回收。一旦調(diào)用.dispose()保留被釋放數(shù)據(jù)可能liable to被垃圾回收。由于“泄漏”風(fēng)險(xiǎn)真實(shí)存在官方強(qiáng)烈建議優(yōu)先用useQueryLoader。從 useQueryLoader.js 的實(shí)現(xiàn)可以看到Hook 用undisposedQueryReferencesRef維護(hù)所有已創(chuàng)建但未釋放的 query reference每當(dāng)新的 query reference 提交commit時(shí)會遍歷并釋放所有“早于當(dāng)前提交”的引用組件卸載時(shí)也會全部釋放。它還區(qū)分了 Live 查詢調(diào)用dispose()與普通查詢調(diào)用releaseQuery()只釋放數(shù)據(jù)、保留網(wǎng)絡(luò)請求語義非常細(xì)致。不能在 React 渲染階段調(diào)用loadQuery()若在 React 的渲染階段render phase被調(diào)用會拋出錯(cuò)誤。它應(yīng)當(dāng)被放在事件回調(diào)、Effect 或路由/EntryPoint 等非渲染上下文中調(diào)用。useQueryLoader返回的loadQuery回調(diào)同樣遵守此約束。底層機(jī)制急切執(zhí)行、去重與可重放從 loadQuery.js 的實(shí)現(xiàn)可以還原loadQuery的幾個(gè)關(guān)鍵底層設(shè)計(jì)急切執(zhí)行eager execution與通?!坝嗛喓蟛砰_始執(zhí)行”的 Observable 不同loadQuery在調(diào)用時(shí)就同步發(fā)起請求。源碼用ReplaySubject捕獲急切執(zhí)行期間發(fā)生的事件再通過Observable.create返回一個(gè)可重放replay這些事件的 Observable見executionSubject/returnedObservableloadQuery.js。這樣后續(xù)訂閱者如usePreloadedQuery能拿到從請求開始到訂閱時(shí)刻的全部事件。雙層去重原始網(wǎng)絡(luò)請求去重以raw-network-request- getRequestIdentifier(params, variables)為標(biāo)識調(diào)用fetchQueryDeduped保證同一 (environment, 請求標(biāo)識) 只有一個(gè)網(wǎng)絡(luò)請求在途。即使查詢 AST 尚未加載、loadQuery被多次調(diào)用網(wǎng)絡(luò)請求也不會重復(fù)見 loadQuery.js。操作執(zhí)行去重以operation.request.identifier為標(biāo)識再次fetchQueryDeduped保證對同一操作的執(zhí)行只處理一次響應(yīng)同時(shí)為 Suspense 基礎(chǔ)設(shè)施跟蹤操作的活動狀態(tài)見executeDedupedloadQuery.js。store 命中短路當(dāng)fetchPolicy為store-or-network且environment.check(operation).status available數(shù)據(jù)完整可用時(shí)直接跳過網(wǎng)絡(luò)請求見checkAvailabilityAndExecuteloadQuery.js。Preloadable 分支若傳入的是PreloadableConcreteRequest且查詢模塊已注冊PreloadableQueryRegistry.get(queryId)非空則立即執(zhí)行否則先發(fā)網(wǎng)絡(luò)請求再注冊onLoad回調(diào)等模塊就緒后補(bǔ)齊執(zhí)行見 loadQuery.js。與preloadQuery_DEPRECATED的對比倉庫中保留了舊 API preloadQuery_DEPRECATED.js兩者核心差異維度loadQuerypreloadQuery_DEPRECATED寫入 Store 時(shí)機(jī)查詢與數(shù)據(jù)就緒即寫只有查詢傳給usePreloadedQuery后才寫返回類型PreloadedQueryInner含dispose/releaseQuery/cancelNetworkRequestPreloadedQueryInner_DEPRECATED含status去重/緩存fetchQueryDeduped 網(wǎng)絡(luò)層去重pendingQueriesByEnvironmentMap 30 秒過期清理DEFAULT_PREFETCH_TIMEOUT默認(rèn) fetchPolicy按請求類型動態(tài)選擇固定store-or-network對調(diào)用者的要求必須自行dispose()或交給useQueryLoader同樣需要由消費(fèi)方管理生命周期舊 API 通過pendingQueriesByEnvironmentWeakMap 按 Environment 分組以 cacheKeygetRequestIdentifier(params, variables)拼接可選fetchKey去重并在訂閱取消或請求完成后延遲 30 秒清理緩存條目這些實(shí)現(xiàn)細(xì)節(jié)在新 API 中被fetchQueryDeduped取代。常見錯(cuò)誤與最佳實(shí)踐小結(jié)在渲染階段調(diào)用loadQuery會拋錯(cuò)。請把調(diào)用放到事件回調(diào)、useEffect或路由/EntryPoint 預(yù)加載邏輯中。忘記dispose()query reference 持續(xù)被 Store 保留數(shù)據(jù)泄漏。要么手動在不再需要時(shí)調(diào)用dispose()要么優(yōu)先用useQueryLoaderHook 會在新引用提交、組件卸載時(shí)自動釋放。頂層調(diào)用不要在模塊頂層無條件調(diào)用loadQuery應(yīng)讓它在響應(yīng)事件路由導(dǎo)航、點(diǎn)擊等時(shí)觸發(fā)。直接操作返回值的其他字段返回格式不穩(wěn)定未來版本可能變化。只應(yīng)把結(jié)果整體傳給usePreloadedQuery()若確實(shí)需要 dispose只依賴dispose()?;煜?Store 緩存與網(wǎng)絡(luò)緩存fetchPolicy控制的是 Relay Store 層是否復(fù)用數(shù)據(jù)networkCacheConfig控制的是網(wǎng)絡(luò)層可能存在的 query response cache。默認(rèn){force: true}會繞過網(wǎng)絡(luò)層響應(yīng)緩存。延伸閱讀usePreloadedQuery 文檔消費(fèi) query reference 的 Hook 完整 API。useQueryLoader 文檔自動管理 query reference 生命周期的推薦入口。Fetch Policies 指南 與 數(shù)據(jù)在場性指南fetchPolicy與垃圾回收的底層語義。源碼loadQuery實(shí)現(xiàn)見 loadQuery.js相關(guān)類型見 EntryPointTypes.flow.js測試用例見 loadQuery-test.js 與 loadQuery-source-behavior-test.js。贊分享前端開發(fā)工具【免費(fèi)下載鏈接】relayRelay is a JavaScript framework for building>項(xiàng)目地址https://gitcode.com/gh_mirrors/relay29/relay點(diǎn)擊查看免費(fèi)下載相關(guān)推薦Relay loadQuery 完全指南以命令式預(yù)取實(shí)現(xiàn) render-as-you-fetchRelay loadQuery 完全指南以命令式預(yù)取實(shí)現(xiàn) render as you fetch loadQuery 是 react relay 提供的一個(gè)命前端開發(fā)工具Relay loadQuery 完整指南用命令式預(yù)加載實(shí)現(xiàn) render-as-you-fetch 數(shù)據(jù)獲取Relay loadQuery 完整指南用命令式預(yù)加載實(shí)現(xiàn) render as you fetch 數(shù)據(jù)獲取 loadQuery 是 RelayReact前端開發(fā)工具Relay 的 loadQuery 命令式數(shù)據(jù)預(yù)取 API 全解配合 usePreloadedQuery 實(shí)現(xiàn) render-as-you-fetchRelay 的 loadQuery 命令式數(shù)據(jù)預(yù)取 API 全解配合 usePreloadedQuery 實(shí)現(xiàn) render as you fetch loa前端開發(fā)工具創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考