news 2026/9/23 7:34:18

Relay 19 的 `fetchQuery` 完整指南:React 之外的命令式查询获取、去重与数据保留

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Relay 19 的 `fetchQuery` 完整指南:React 之外的命令式查询获取、去重与数据保留

Relay 19 的fetchQuery完整指南:React 之外的命令式查询获取、去重与数据保留

【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relay

fetchQuery是 Relay 官方提供的命令式(imperative)查询 API,用于在 React 组件渲染之外主动发起 GraphQL 查询并读取结果。它返回一个基于RelayObservable的可观察对象,支持subscribe/toPromise两种消费方式,并内置同请求去重、自动写入 Relay Store 等能力。读完本文,你将掌握fetchQuery的参数与返回对象细节、正确的订阅与取消模式、store-or-networknetwork-only两种 fetch policy 的差异、数据保留(GC)陷阱,以及它与toPromiseloadQuery、旧版fetchQuery_DEPRECATED之间的取舍,并可从源码与测试用例层面理解其实现原理。

为什么需要fetchQuery

在 Relay 的应用里,大多数数据获取发生在 React 组件内部,通过useLazyLoadQueryuseFragment等 Hooks 完成,此时 Relay 会自动管理请求的生命周期。但有一类场景发生在“React 之外”:

  • 在事件处理器(例如按钮点击、表单提交)中按需拉取数据;
  • 在路由守卫、服务端脚本、非 React 模块中执行一次性查询;
  • 需要手动控制请求的订阅、取消与错误处理,而不是依赖 Suspense。

fetchQuery正是为这些场景设计的:它在react-relay中导出(同时也在relay-runtime中实现),接受 environment、GraphQL 查询与变量,返回一个可观察对象;只有当你真正订阅(subscribe)或转换为 Promise(toPromise)时,网络请求才会发出。

从仓库的导出入口看,react-relay/index.js 从RelayRuntime解构出fetchQuery并暴露为公开 API(见 react-relay/index.js),同时一并导出了旧版fetchQuery_DEPRECATED(react-relay/index.js),方便不同代际代码共存迁移。

基本用法:在 React 之外发起查询

官方文档给出的最小示例是通过.subscribe()订阅一个查询:

// 优先使用 useRelayEnvironment() 返回的 environment const MyEnvironment = require('MyEnvironment'); const {fetchQuery} = require('react-relay'); fetchQuery( environment, graphql` query AppQuery($id: ID!) { user(id: $id) { name } } `, {id: 4}, ).subscribe({ start: () => {...}, complete: () => {...}, error: (error) => {...}, next: (data) => {...}, });

关键点:

  • environment:承载查询执行的 Relay Environment 实例。如果你在 React 组件内发起请求,应该使用useRelayEnvironment()拿到的环境,以保证与组件共享同一个 Store 和网络层;
  • query:用graphql模板字面量书写的查询。编译期会把它转换为GraphQLTaggedNode,运行时通过getRequest()解析(见 fetchQuery.js);
  • variables:与查询内声明变量匹配的对象,如{id: 4}
  • options(可选):见下文参数表。

参数一览

参数类型说明
environmentIEnvironment执行请求的 Relay Environment 实例
queryGraphQLTaggedNodegraphql模板字面量定义的查询
variablesVariables与查询声明变量匹配的变量对象
options.fetchPolicy'network-only' \| 'store-or-network'获取策略,默认'network-only'
options.networkCacheConfig.forceboolean是否绕过网络响应缓存,默认为true

从 relay-runtime/query/fetchQuery.js 的函数签名可以看到,fetchQueryoptions实际支持两个字段:fetchPolicynetworkCacheConfig(后者即CacheConfig类型)。实现中networkCacheConfig默认被展开为{force: true, ...}(fetchQuery.js),也就是说默认每次调用都会绕过网络响应缓存、直接打网络;只有当你显式传入force: false时才可能命中RelayQueryResponseCache之类的网络层缓存。CacheConfig在 RelayRuntimeTypes.js 中定义,还包含pollliveConfigIdmetadatatransactionId等字段,可用于轮询、实时订阅透传等高级用途。

注:虽然 API 参考文档只列了networkCacheConfig.force一个选项,但当前仓库版本(Relay 19 代码线)的fetchQuery实现已额外支持fetchPolicyFetchQueryFetchPolicy类型只包含'store-or-network' | 'network-only'两个合法值,传入其他值会抛出'fetchQuery: Invalid fetchPolicy'错误(fetchQuery.js)。

返回的可观察对象:subscribe 与 toPromise

fetchQuery返回一个RelayObservable。请求不会在调用时自动发出——只有调用.subscribe().toPromise()才会真正启动网络请求。

.subscribe(observer)

subscribe接受一个 observer 对象,可为以下事件注册回调:

回调触发时机收到的参数
start网络请求开始时subscription:网络可观察对象上的订阅
next每次从网络收到 payload 时data:收到 payload 时从 Relay Store 读取的查询数据快照
complete网络请求成功完成时
error网络请求出错时error:发生的错误
unsubscribe订阅被取消时subscription:网络可观察对象上的订阅

subscribe的返回值是一个subscription对象,调用subscription.unsubscribe()可以取消正在进行的网络请求。这一行为在源码注释中有明确描述:“如果 subscribe 返回的 disposable 在请求 in-flight 期间被调用,请求将被取消”(fetchQuery.js)。

需要注意:subscribe只是订阅“查询的获取过程”,并不会订阅 Relay Store 中数据的后续变化。如果你需要持续跟踪数据变更,应该改用useSubscribeToInvalidationStateobserveFragmentExperimental或组件内的 Hooks 方案。

.toPromise()

toPromise()把可观察对象转换为 Promise:

const {fetchQuery} = require('react-relay'); fetchQuery( environment, graphql` query AppQuery($id: ID!) { user(id: $id) { name } } `, {id: 4}, ) .toPromise() // 注意:不建议使用,可能导致数据缺失! .then(data => {...}) .catch(error => {...});

官方文档对toPromise给出了明确警告:Promise 在第一个网络响应到达时就会 resolve,并取消后续处理。这意味着查询中带有的 deferred(@defer)、3D(@module)等增量数据可能不会被处理,结果对象中会缺失这些部分。因此官方明确表示一般不建议使用toPromise()。此外,toPromise返回的 Promise 无法被取消。

两种 fetchPolicy 的行为差异

fetchPolicy决定fetchQuery如何满足数据请求:

  • network-only(默认):无条件向网络发起请求,然后从 Store 读取最新快照作为next的数据。这是文档所述“默认每次绕过网络响应缓存”的行为在 Store 层面的对应。
  • store-or-network:先调用environment.check(operation)检查 Store 中查询数据是否完整可用;如果状态为available,直接environment.lookup()返回本地数据,不再发网络请求;否则退回网络获取(fetchQuery.js)。

实现中,无论哪种策略,最终都经由fetchQueryInternal.fetchQuery()执行网络层请求,并把每次响应写回 Store 后再通过environment.lookup(operation.fragment)读出数据快照(fetchQuery.js)。同时实现会通过environment.__log({name: 'fetchquery.fetch', ...})记录一次内部日志事件,方便你接入 Relay DevTools 或自定义日志观察该次获取的决策过程(fetchQuery.js)。

三大行为特性:自动写库、不保留数据、自动去重

1. 自动写入 Store

fetchQuery会把服务端返回的数据自动规范化并写入内存中的 Relay Store,同时通知所有订阅了相关数据的组件重新渲染。也就是说,即使组件没有直接发起这次查询,只要它订阅了查询涉及的 fragment 数据,就会收到更新。

2. 不保留数据(GC 陷阱)

fetchQuery不会对查询数据执行retain(),因此请求完成后数据随时可能被 Relay 的垃圾回收机制从 Store 中清除——它不保证数据在请求作用域之外仍然存在。官方文档建议:如果你需要在请求之外保住这些数据,请直接调用environment.retain()持有查询引用。详见文档 Controlling Relay's GC Policy(仓库中对应指南位于 website/versioned_docs/version-v17.0.0/guided-tour/reusing-cached-data)。

这一行为同样被测试用例直接验证:在 relay-runtime/query/tests/fetchQuery-test.js 中,测试'fetches request and does not retain data'mock 了environment.retain,在complete回调里断言retained.length === 0,证实fetchQuery全程不会触发 retain。

3. 同请求自动去重

fetchQuery会对“相同 environment、相同查询、相同变量”且同时 in-flight的请求自动去重:后发起的调用会复用第一个请求的响应,而不是重复打网络。去重缓存的核心实现位于 relay-runtime/query/fetchQueryInternal.js:

  • 每个 environment 对应一个Map<RequestIdentifier, RequestCacheEntry>RequestIdentifier由查询与变量唯一确定(见 getRequestIdentifier);
  • 请求首次启动时创建RelayReplaySubject缓存所有网络响应事件,后续同标识订阅者直接从这个 subject 拿到回放;
  • 请求完成(成功或失败)后缓存条目被finally清理;若某个 in-flight 请求的所有订阅者都取消订阅,底层网络订阅也会被取消并从缓存中移除(fetchQueryInternal.js)。

一个需要注意的边界:如果请求是同步完成的(例如命中同步缓存),在同一个 tick 内再次调用相同参数的fetchQuery不会去重,因为此时请求已经不在 in-flight 状态(fetchQueryInternal.js)。

在组件内使用:配合useRelayEnvironment()

虽然fetchQuery面向 React 之外的场景,但官方文档特别指出:如果在 React 组件内发起请求,应使用useRelayEnvironment()获取 environment。示例:

import {useCallback} from 'react'; import {useRelayEnvironment, fetchQuery, graphql} from 'react-relay'; function useRefreshUser(id) { const environment = useRelayEnvironment(); return useCallback(() => { fetchQuery(environment, UserQuery, {id}).subscribe({ complete: () => console.log('user refreshed'), error: (error) => console.error(error), }); }, [environment, id]); }

这样做能保证该请求与当前 React 树共享同一个 Store 与网络层,写回的数据可以被组件内相关 fragment 立即观察到。

与其他 API 的关系与取舍

  • useLazyLoadQuery/loadQuery:面向组件内/EntryPoint 的数据预加载,由 React 或 Router 管理生命周期与数据保留,是“响应式获取”的首选;
  • fetchQuery:面向一次性、命令式获取,数据不保留、需手动订阅或转 Promise,适合事件驱动场景;
  • fetchQuery_DEPRECATED:旧版 API,直接返回 Promise,内部等价于environment.execute(...).map(lookup).toPromise()(见 relay-runtime/query/fetchQuery_DEPRECATED.js)。它天然具备toPromise的所有缺点(首个响应即 resolve、无法取消),在新代码中应优先使用返回 observable 的新版fetchQuery
  • environment.execute:更底层的网络执行入口,fetchQuery在去重与快照读取之上对它的封装。

总结

fetchQuery是 Relay 在 React 之外执行查询的官方入口:调用后返回RelayObservable,订阅才开始发请求;数据自动写入 Store,但不会 retain,请求结束后可能被 GC;相同参数的 in-flight 请求会被自动去重。日常使用中建议:

  1. 组件内使用useRelayEnvironment()获取 environment;
  2. 优先用.subscribe()而非.toPromise(),避免 deferred / 3D 数据丢失;
  3. 若需在请求结束后长期保留数据,显式调用environment.retain()
  4. 需要命中 Store 时使用store-or-network,需要强制走网络(默认行为)时保持network-only

【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relay

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 7:33:44

一块陶泥在拉坯机上的旋转与前端无级缩放的向心对称

一块陶泥在拉坯机上的旋转与前端无级缩放的向心对称在美院陶艺工坊幽静的下午&#xff0c;空气中弥漫着高岭土与潮湿泥浆的清香。 每一个初学陶艺的人&#xff0c;在拉坯机&#xff08;Potters Wheel&#xff09;前遭遇的第一场残酷洗礼&#xff0c;叫做——“找正&#xff08;…

作者头像 李华
网站建设 2026/9/23 7:33:29

弹簧质点系统(Mass-Spring System):Canvas 模拟软胶果冻物理抖动

弹簧质点系统&#xff08;Mass-Spring System&#xff09;&#xff1a;Canvas 模拟软胶果冻物理抖动在现代高阶 UI 微交互、生动吉祥物萌宠动画以及先锋触控界面设计中&#xff0c;“果冻/软胶布丁般的柔体抖动质感&#xff08;Soft-Body Jiggle Physics&#xff09;” 是一种能…

作者头像 李华
网站建设 2026/9/23 7:28:14

Java全栈开发实战:亲子互动平台架构与实现

1. 项目背景与核心价值作为一名在Java全栈开发领域深耕多年的技术人&#xff0c;我见过太多缺乏实战价值的毕业设计项目。这个亲子互动平台的设计初衷&#xff0c;是要解决当代家庭教育中三个核心痛点&#xff1a;亲子陪伴时间碎片化、互动形式单一化、成长记录分散化。根据中国…

作者头像 李华
网站建设 2026/9/23 7:27:13

从拖延到发布:我如何写出第一篇博客并坚持下去

我第一次真正把一篇博客发出去的时候&#xff0c;距离我注册好域名整整过去了十一个月。那十一个月里我换了三次博客主题、研究过十几套评论插件、甚至给文章分类都想好了七八个名字&#xff0c;但正文一个字都没写。最后把我从这种"准备永动机"里拽出来的&#xff0…

作者头像 李华
网站建设 2026/9/23 7:26:38

消息队列内存数据中心架构设计与优化实践

1. 内存数据中心的架构设计在消息队列系统中&#xff0c;MemoryDataCenter扮演着至关重要的角色。作为整个系统的内存中枢&#xff0c;它负责管理所有运行时数据&#xff0c;包括交换机、队列、绑定关系以及消息本身。这种全内存的设计理念源于对高性能的极致追求——相比磁盘I…

作者头像 李华
网站建设 2026/9/23 7:26:13

Claude Code 知识工作插件实战:用 slash commands 封装高效工作流

1. 从标题说起&#xff1a;knowledge-work-plugins 到底是个什么定位第一次看到knowledge-work-plugins这个仓库名&#xff0c;我的直觉是&#xff1a;这不是一个普通的小工具&#xff0c;而是一套面向“知识工作者”的插件集合。知识工作者这个词覆盖面很广——写代码的、写文…

作者头像 李华