Relay 的 loadQuery以命令式预加载实现 render-as-you-fetch 的完整指南【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relayloadQuery是 Relay 数据获取体系中的命令式入口用于在 React 渲染之外主动发起查询请求、将查询引用Query Reference保留在 Relay store 中并与usePreloadedQuery()搭配实现 render-as-you-fetch边取边渲染模式。本文以 Relay 仓库中 loadQuery API 参考文档 为主体骨架结合 loadQuery 实现源码、类型定义与测试用例系统讲解其参数语义、返回结构、底层执行流程与内存管理注意事项帮助你掌握在路由导航、点击等事件回调中提前加载数据、随后在组件内同步消费的完整实战方案。loadQuery是什么loadQuery是react-relay包导出的一个命令式函数。与useLazyLoadQuery这类渲染时才发起请求的 Hook 不同它允许你在组件渲染之前、在事件回调中主动发起查询从而提前并行地拉取数据这正是 render-as-you-fetch 的核心思想调用loadQuery()立即开始取数把返回的查询引用query reference传给usePreloadedQuery()在渲染时读取 store 中的数据数据未就绪时usePreloadedQuery会触发 Suspense 挂起就绪后返回查询结果。官方文档明确建议loadQuery与 usePreloadedQuery 搭配使用同时优先推荐useQueryLoader因为它会在组件卸载或引用不再需要时自动对查询引用调用.dispose()避免手动管理内存泄漏。参见 useQueryLoader 文档。需要特别强调的是如果从loadQuery返回的查询引用没有被调用.dispose()它会持续把数据保留retain在 Relay store 中造成数据泄漏、无法被垃圾回收。这是使用本 API 时必须始终铭记的一个前提。基本用法文档给出的最简示例见 load-query.mdconst MyEnvironment require(MyEnvironment); const {loadQuery} require(react-relay); const query graphql query AppQuery($id: ID!) { user(id: $id) { name } } ; // 注意一般不应在模块顶层调用 loadQuery // 而应在事件路由导航、点击等中调用。 const queryReference loadQuery( MyEnvironment, query, {id: 4}, {fetchPolicy: store-or-network}, ); // 稍后把 queryReference 传给 usePreloadedQuery() // 注意查询引用应当调用 .dispose()本例中省略了。两个关键约定不要在渲染阶段调用loadQuery()如果在 React 的 render phase 中被调用会直接抛错文档 Behavior 一节明确说明loadQuery与useQueryLoader返回的loadQuery回调都会在 render 阶段抛错。它应放在路由导航、按钮点击等事件回调中。及时 dispose示例注释专门提醒查询引用应当调用.dispose()只是因为演示而省略。真实代码中应通过useQueryLoader自动管理或在组件卸载时手动释放。与之配合的消费端完整示例来自 use-preloaded-query.mdconst React require(React); const {graphql, useQueryLoader, usePreloadedQuery} require(react-relay); const AppQuery graphql query AppQuery($id: ID!) { user(id: $id) { name } } ; 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; }在这个模式中点击按钮时loadQuery({id: 4})立即取数渲染层通过usePreloadedQuery消费查询挂起期间显示 Suspense fallback。相比useLazyLoadQuery这允许更早开始取数同时不阻塞渲染。参数详解environment一个 Relay Environment 实例用于执行请求。如果请求是在某个 React 组件内部发起的通常应该使用useRelayEnvironment获取当前环境const environment useRelayEnvironment();query要获取的 GraphQL 查询有两种指定方式使用graphql模板字面量声明的查询或者一个可预加载的具体请求preloadable concrete request通过 requirename-of-query$Parameters.graphql文件获得。需要强调的是Relay 编译器只有在查询标注了preloadable指令时才会生成$Parameters文件。也就是说想让loadQuery拿到先发网络请求、后加载查询 AST的能力查询必须声明为preloadableconst query graphql query AppQuery($id: ID!) preloadable { user(id: $id) { name } } ;这一约束在实现源码中也有印证loadQuery通过PreloadableQueryRegistry查询已加载的模块若传入的是PreloadableConcreteRequest且 AST 尚未同步可用它会立即发起网络请求并注册onLoad回调等待 AST 加载完成后补做 store 写入与执行见 loadQuery.js同时源码对缺少 persisted query id 的可预加载请求会抛出Relay: \loadQuery requires that preloadable query ... has a persisted query id 的 invariant 断言见 loadQuery.js对应测试用例也注释了Only queries with an ID are preloadable见 loadQuery-test.js。variables包含查询变量的对象必须与查询内部声明的 GraphQL 变量一一匹配。例如上例中查询声明了$id: ID!调用时就要传{id: 4}。options可选可选的选项对象包含以下键fetchPolicy决定缓存与网络请求策略控制是否复用本地缓存数据以及基于 Relay store 中当前缓存数据的可用性是否发起网络请求。具体取值可参考 Fetch Policies 指南取值语义是否复用本地缓存是否发起网络请求store-or-network默认复用本地缓存仅当查询有数据缺失时才发起网络请求若查询已完整缓存则不发起网络请求是有数据缺失时才发起store-and-network复用本地缓存并且总是发起网络请求无论本地缓存是否缺失是总是发起network-only不复用本地缓存总是发起网络请求拉取查询忽略本地已有数据否总是发起此外fetch-policies.md 还补充了第 4 种策略store-only只复用本地缓存、从不发起网络请求适合读取和操作纯本地数据或由调用方自行负责取数。该策略也出现在loadQuery的默认策略推断中——见下文源码级原理小节。数据是否缺失/过期的判定细节可参考 Availability of Data、Presence of Data 与 Staleness of Data 指南。networkCacheConfig可选默认值为{force: true}。包含网络层的缓存配置。注意网络层可能带有额外的查询响应缓存会为完全相同的查询复用网络响应。默认行为是彻底绕过该缓存也就是传{force: true}。在实现中该默认值被硬编码合并进选项——源码第 115-118 行总是把force: true合并进networkCacheConfig见 loadQuery.js测试中也能看到传给executeWithSource的 operation 其cacheConfig恒为{force: true}见 loadQuery-test.js。environmentProviderOptions可选透传给prepareSurfaceEntryPoint.js中environmentProvider的选项对象。类型定义为EnvironmentProviderOptions {readonly [string]: unknown, ...}见 EntryPointTypes.flow.js它只是被携带在返回的查询引用上供 EntryPoint 环境提供方使用。返回值查询引用Query ReferenceloadQuery返回一个查询引用官方 API 文档中明确保证可用/推荐的属性只有一个dispose释放查询引用被 store 保留retain的能力。调用后该查询引用所引用的数据就可能被垃圾回收。文档同时给出强烈警告返回值的具体格式不稳定、未来很可能变化强烈不建议使用其他任何属性否则升级 Relay 版本时极易出错。正确姿势是把loadQuery()的结果直接传给usePreloadedQuery()。不过从 EntryPointTypes.flow.js 的类型定义和 loadQuery.js 的实际返回对象看该引用还包含以下内部属性仅供理解实现不建议业务依赖属性说明kind固定为PreloadedQueryenvironment发起请求的 EnvironmentfetchKey每次调用loadQuery递增的唯一键用于让usePreloadedQuery的 Suspense 缓存区分同查询的不同引用fetchPolicy本次生效的 fetch 策略id/name查询的持久化 id 与名称networkCacheConfig网络层缓存配置恒含force: truevariables查询变量networkError网络请求失败时保存的错误getterisDisposed是否已释放gettersource网络/执行事件的可观察流Observable用于订阅取数进度dispose/releaseQuery/cancelNetworkRequest释放保留、仅释放数据、仅取消进行中的网络请求environmentProviderOptions透传的环境提供方选项其中releaseQuery只释放数据保留cancelNetworkRequest只取消在途网络请求dispose则同时执行两者且幂等见 loadQuery.js。TypeScript 侧的签名同样只对外暴露environment / preloadableRequest / variables / options / environmentProviderOptions五个参数并返回PreloadedQuery见 loadQuery.d.ts。行为语义Behavior取数与写库时机loadQuery()传入普通查询时会直接获取数据传入可预加载的具体请求时会同时获取数据与查询 AST。一旦查询和数据都可用查询返回的数据就会被写入 store。这一点与preloadQuery_DEPRECATED不同——后者只有在查询被传给usePreloadedQuery时才会把数据写入 store。对应的测试用例覆盖了数据到达后写入 store与dispose 后不再写库的行为见 loadQuery-test.js。数据保留与垃圾回收从loadQuery返回的查询引用会被 Relay store保留retain防止其数据被垃圾回收。一旦对查询引用调用.dispose()数据就可能被垃圾回收。测试中对environment.retain的断言贯穿始终——即使在store-or-network且 store 可完整满足查询不发起任何网络请求的情况下查询也仍然会被 retain见 loadQuery-test.js这与文档查询引用会被保留的描述完全一致。渲染阶段调用会抛错loadQuery()如果在 React 渲染阶段被调用会抛出错误。因此应始终在事件回调中使用。源码级原理loadQuery内部是如何工作的结合 loadQuery.js 的实现可以还原loadQuery的完整执行链路帮助你更准确地理解其行为1. 生成新的 fetchKey每次调用loadQueryfetchKey都会递增见 loadQuery.js。这样即使对同一个查询、同样的变量多次调用每个查询引用也会被usePreloadedQuery独立求值避免 Suspense 缓存复用旧结果而跳过必要的 refetch。2. 推断默认 fetchPolicy如果没有显式传入fetchPolicy源码会按以下规则推断默认值见 loadQuery.js若查询带livemetadata或启用了执行期解析器exec-time resolvers默认策略为store-and-network常量DEFAULT_LIVE_FETCH_POLICY若查询既无id也无text即纯客户端查询默认策略为store-only其余情况默认store-or-networkDEFAULT_FETCH_POLICY。测试用例uses the exec-time default when the available AST enables exec-time resolvers验证了执行期解析器场景下默认策略变为store-and-network且确实发起了网络请求见 loadQuery-test.js。3. 检查 store 可用性并决定是否取数通过environment.check(operation)判断操作能否被 store 满足。逻辑为只要策略不是store-or-network或者environment.check(...)返回状态不是available就发起执行见 loadQuery.js。源码注释特别指出environment.check可能通过 missing field handlers 触发 store 更新因此短路判断可以避免不必要的更新。4. 网络请求去重无论原始网络请求还是操作执行都通过fetchQueryDeduped按(environment, identifier)去重——同一时间只有一条在途请求见 loadQuery.js。原始网络请求使用raw-network-request- getRequestIdentifier(params, variables)作为额外键与操作执行的去重区分开。这样即使查询 AST 尚未加载、loadQuery被多次调用网络请求也只会发一次。5. 可预加载请求的特殊路径当传入的是PreloadableConcreteRequest且查询 AST 尚未同步可用时loadQuery会立即发起网络请求只要策略不是store-only并通过PreloadableQueryRegistry.onLoad(queryId, callback)注册回调AST 加载完成后再创建 operation、retain 并把网络响应接入 store 执行见 loadQuery.js。这正是preloadable查询可以实现代码与数据并行加载的根本原因。对应测试覆盖了 AST 不可用时的网络请求、onLoad 回调执行、dispose 行为等见 loadQuery-test.js。6. 非惰性执行与 ReplaySubject与常规 Observable 的惰性执行不同loadQuery希望在调用时立即开始取数。实现使用中间层ReplaySubject捕获急切执行期间产生的事件再把这些事件重放到最终返回给调用方的 Observable 上见 loadQuery.js。dispose时若已进入执行阶段则退订执行流否则退订网络请求流并调用PreloadableQueryRegistry的cancelOnLoadCallback见 loadQuery.js。实战要点与注意事项综合文档与源码使用loadQuery时应遵循以下要点优先使用useQueryLoader它会替你在组件卸载或引用不再可访问时自动 dispose 查询引用避免数据泄漏。只有在你需要完全掌控生命周期例如路由层面预取、EntryPoint 场景时才直接使用loadQuery并手动管理dispose。在事件回调中调用loadQuery以及useQueryLoader的loadQuery回调都会在 React 渲染阶段抛错务必在点击、路由导航等事件处理器中调用。选择正确的 fetchPolicy导航回看场景用默认的store-or-network即可快速展示缓存需要先显示缓存再后台刷新用store-and-network需要绝对新鲜数据用network-only纯客户端数据用store-only。按需使用preloadable只有标注preloadable的查询才会生成$Parameters.graphql文件从而支持代码分割与数据请求并行发起普通graphql字面量查询也能传给loadQuery但走的是 AST 同步可用的路径。不要依赖返回值的内部结构查询引用的精确格式不稳定仅把loadQuery的结果交给usePreloadedQuery需要释放时调用.dispose()。理解数据写入时机与preloadQuery_DEPRECATED不同loadQuery在数据到达后立即写入 store因此即便查询引用尚未被组件消费store 中也已包含数据Suspense 恢复渲染时可以直接读取。关联资源如需深入掌握相关 API 与机制可继续阅读仓库内以下资料API 文档loadQuery、usePreloadedQuery、useQueryLoader指南Fetch Policies、Availability of Data、Presence of Data、Rendering Queries实现源码loadQuery.js、EntryPointTypes.flow.js、loadQuery.d.ts测试用例loadQuery-test.js、loadQuery-store-behavior-test.js、loadQuery-source-behavior-test.js【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relay创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
