React Relay requestSubscription 深度指南:命令式建立 GraphQL 订阅的完整实战
2026/9/21 16:20:33 网站建设 项目流程

React Relay requestSubscription 深度指南:命令式建立 GraphQL 订阅的完整实战

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

requestSubscription是 React Relay 提供的命令式(Imperative)API,用于在 JavaScript 代码中直接建立一条 GraphQL Subscription,从而在服务端事件流到达时实时获取并更新数据。本指南以requestSubscription的官方 API 参考文档为主体,结合relay-runtime中该 API 的实际源码实现与测试用例,系统讲解它的参数、配置、返回类型、底层执行链路,以及与 Hook 版useSubscription的关系,帮助你掌握在非组件场景(如工具函数、事件处理器、数据层逻辑)中安全使用订阅的完整方案。

requestSubscription是什么

requestSubscription定义在react-relay包中,是一个命令式 API:调用它时传入一个 Relay Environment 与一份订阅配置,Relay 会立即通过该 Environment 建立订阅,并返回一个可用于取消订阅的Disposable对象。

import {graphql, requestSubscription} from 'react-relay'; const subscription = graphql` subscription UserDataSubscription($input: InputData!) { # ... } `; function createSubscription(environment: IEnvironment): Disposable { return requestSubscription(environment, { subscription, variables: {input: {userId: '4'}}, }); }

与声明式的useSubscriptionHook 不同,requestSubscription不依赖 React 组件的生命周期:你可以把它放在任意普通函数、事件回调或非 React 模块中,只要手里有一个 Environment 实例即可建立订阅,并由调用方自行管理订阅的销毁时机。

关于订阅的整体概念,包括订阅根字段(subscription root field)、服务端事件流与查询分离的两段式处理模型,可参阅 GraphQL subscriptions 引导教程;关于如何在 React 组件中以声明式方式订阅,可参阅 useSubscription API 与 Updating Data 引导章节。

参数(Arguments)

requestSubscription接受两个参数:

参数类型说明
environmentIEnvironment一个 Relay Environment 实例,订阅将建立在该 Environment 之上
configGraphQLSubscriptionConfig描述订阅如何建立、如何处理数据与错误的配置对象

config中最核心的三个字段是:

  • subscription:一个GraphQLTaggedNode,即用graphql模板字面量声明的 Subscription 操作;
  • variables:传给订阅的变量对象;
  • 一系列可选回调与更新器(onCompletedonErroronNextupdater),用于处理订阅生命周期内的各类事件。

在 GraphQLSubscriptionConfig 类型参考 中,完整的字段定义如下。

GraphQLSubscriptionConfig<TSubscriptionPayload>完整配置详解

GraphQLSubscriptionConfig是一个泛型配置对象,其字段含义如下:

字段必选类型说明
subscriptionGraphQLTaggedNode使用graphql模板字面量声明的 GraphQL Subscription
variablesVariables传给订阅的变量
cacheConfig可选CacheConfig订阅的缓存与执行相关配置
onCompleted可选() => void订阅成功建立时执行的回调
onError可选(Error) => {}发生错误时执行的回调
onNext可选(TSubscriptionPayload) => {}收到新数据时执行的回调
updater可选SelectorStoreUpdater收到订阅负载后如何更新 Relay Store 的更新器函数

结合源码relay-runtime中的类型定义(见 requestSubscription.js 第 44–54 行),该类型还额外包含一个可选的configs字段,用于传入声明式变更配置(Declarative Mutation Config):

export type GraphQLSubscriptionConfig<TVariables, TData, TRawResponse> = Readonly<{ configs?: Array<DeclarativeMutationConfig>, cacheConfig?: CacheConfig, subscription: GraphQLSubscription<TVariables, TData, TRawResponse>, variables: NoInfer<TVariables>, onCompleted?: ?() => void, onError?: ?(error: Error) => void, onNext?: ?(response: ?TData) => void, updater?: ?SelectorStoreUpdater<TData>, }>;

注意:updaterconfigs二者只能二选一。源码中在两者同时传入时会抛出warning提示"Expected only one ofupdaterandconfigsto be provided";若提供了configs,Relay 会通过RelayDeclarativeMutationConfig.convert(...)将其转换为实际的updater(见 requestSubscription.js)。

Flow 类型参数:TSubscriptionPayload

GraphQLSubscriptionConfig是泛型,类型参数TSubscriptionPayload表示订阅向客户端提供的 payload 类型。你应该使用编译器自动生成的.graphql文件导出的类型作为该类型参数,例如:

import type {UserDataSubscription} from './__generated__/UserDataSubscription.graphql';

之后可将UserDataSubscription作为类型参数传入,让onNext回调的参数获得完整的类型检查支持。

cacheConfig:订阅的缓存与执行控制

cacheConfig(完整定义见 CacheConfig 类型参考)是一个可选对象,用于控制订阅在环境层面的执行行为:

字段类型说明
forceboolean(可选)true时无条件发起查询,忽略任何已配置的响应缓存的当前状态
pollnumber(可选)以毫秒为单位的轮询间隔,使查询按该间隔实时更新(该值会被传给setTimeout
liveConfigIdstring(可选)通过调用 GraphQLLiveQuery 实现查询实时更新;表示执行 live query 时网关使用的配置
metadataobject(可选)用户提供的元数据
transactionIdstring(可选)用户提供的值,用于作为某次操作执行的唯一标识

尽管forcepoll等字段更多用于查询场景,订阅同样会把它传给环境执行层——在requestSubscription的源码中,cacheConfig会被直接传给createOperationDescriptor(subscription, variables, cacheConfig),成为操作描述符(Operation Descriptor)的一部分(见 requestSubscription.js)。

updaterSelectorStoreUpdater:如何更新 Relay Store

默认情况下,Relay 会根据订阅的字段选择自动规范化(normalize)负载并合并进 Store。若需要更精细的控制——例如根据负载创建新记录、更新或删除已有记录——可以提供updater回调。

SelectorStoreUpdater是一个签名如下的函数(详见 SelectorStoreUpdater 类型参考):

(store: RecordSourceSelectorProxy, data) => void

通过该接口,你可以命令式地直接读写 Relay Store:既能创建全新的记录,也能更新或删除已有的记录,从而完全掌控 Store 对订阅负载的响应方式。读写 Store 的完整 API 参见 Store 的RecordSourceSelectorProxy参考。

返回值:Disposable

requestSubscription返回一个Disposable对象(接口定义见 Disposable 类型参考),其结构如下:

interface Disposable { dispose: () => void; }

调用dispose()即可清除(取消)该订阅,停止接收后续数据。在组件卸载、页面离开或业务逻辑结束时及时调用dispose,是避免订阅泄漏的关键。在源码中,返回的Disposable实际上是对环境执行结果订阅的unsubscribe的包装:

const sub = environment .executeSubscription({ operation, updater, }) .subscribe({ complete: onCompleted, error: onError, next: responses => { /* ... */ }, }); return { dispose: sub.unsubscribe, };

见 requestSubscription.js。

源码级原理剖析:requestSubscription的执行链路

阅读relay-runtime中 requestSubscription.js 的实现,可以还原这条完整的执行链路:

  1. 校验操作类型:通过getRequest(config.subscription)获取请求定义,若params.operationKind !== 'subscription',立即抛出Error('requestSubscription: Must use Subscription operation')。也就是说,向该 API 传入 query 或 mutation 会在建立订阅前就被拒绝。
  2. 创建操作描述符:调用createOperationDescriptor(subscription, variables, cacheConfig),将订阅、变量与缓存配置打包成环境可执行的操作描述符。
  3. 处理更新器:若提供了configs(声明式配置),通过RelayDeclarativeMutationConfig.convert(...)转换为updater;否则直接使用配置中传入的updater
  4. 执行订阅:调用environment.executeSubscription({operation, updater}),得到一个可观察对象(Observable),并订阅它的completeerrornext三路事件,分别对应onCompletedonErroronNext回调。
  5. 读取并派发新数据:在next回调中,Relay 会从响应中解析extensions.__relay_subscription_root_id(可能是顶层或数组首项中的扩展字段),若存在则据此通过createReaderSelector重新构造选择器,再执行environment.lookup(selector)从 Store 中读取最新的规范化数据,最后把data作为参数调用onNext。这意味着onNext收到的 payload 是经过 Relay Store 规范化、可被组件直接消费的数据。
  6. 返回取消句柄:最终返回{dispose: sub.unsubscribe}

类型参数与泛型约束

在 requestSubscription.d.ts 中,API 被泛型化声明为requestSubscription<TVariables extends Variables, TData, TRawResponse>(environment, config): Disposable,其中TVariablesVariables约束。配合编译器生成的类型,onNext的 payload、variables的字段都具备类型安全。

useSubscriptionHook 的关系

对于 React 组件内的订阅场景,官方推荐使用 Hook 版useSubscription。查看 useSubscription.js 的实现可以发现,它本质上只是requestSubscription的 React 封装:

  • 通过useRelayEnvironment()获取当前环境;
  • useEffect中调用requestSubscription(environment, config)
  • 在 effect 的清理函数中返回dispose,即组件卸载时自动取消订阅;
  • 依赖数组为[environment, config, requestSubscriptionFn],意味着config对象需要被 memoize,否则每次渲染都会重新订阅(源码注释中明确提示:请勿内联定义配置对象)。

两者适用范围可归纳为:

场景推荐 API
React 组件内部,订阅生命周期跟随组件useSubscription
普通函数、事件处理器、工具模块、非 React 代码requestSubscription
需要在多环境 / 手动控制销毁时机的场景requestSubscription

行为与注意事项

根据 API 参考文档(Behavior 一节)并结合源码,使用requestSubscription时需要注意:

  • 命令式建立订阅:调用即建立,不依赖组件渲染;订阅由返回的Disposable负责清理。
  • 只接受 Subscription 操作:传入其他操作类型会立即抛错("Must use Subscription operation"),这是源码层面强制的约束。
  • onCompleted语义:按文档定义,onCompleted是"订阅建立成功时"执行的回调;它被绑定到可观察对象的complete事件上,具体触发时机取决于 Environment 网络层的实现。
  • onNext的 payload 类型:payload 是订阅负载经过 Relay Store 规范化后的数据。若响应扩展中存在__relay_subscription_root_id,Relay 会用它作为新的 root id 重新 lookup,确保多个订阅复用同一根记录时数据读取正确。
  • 更新器的二选一约束updaterconfigs不能同时提供;同时提供时源码会给出 warning,并优先走configs的声明式转换路径。
  • 事件流与字段选择无关:如 GraphQL subscriptions 引导教程 所述,订阅事件流可以与所选字段完全无关——即服务端事件发生并不保证所选值一定变化,客户端仍会收到通知。
  • 记得清理:在页面卸载、组件销毁或业务结束时调用dispose(),避免订阅句柄泄漏导致内存占用与不必要的网络连接。

测试验证

仓库中的测试 requestSubscription-test.js 覆盖了该 API 的核心行为,包括:

  • 使用requestSubscription(environment, {...})建立订阅并断言其回调行为;
  • 专门的describe('requestSubscription() cacheConfig', ...)测试块,验证cacheConfig的传递路径;
  • updater/configs组合、onNext数据读取等分支的覆盖。

这些测试与上述源码分析相互印证,可作为理解requestSubscription契约行为的补充参考。实际项目中,若想验证自定义订阅逻辑,也可以参考测试中对MockEnvironment与 payload 注入的使用方式,在单元测试中模拟服务端推送。

相关参考

  • requestSubscription API 参考文档
  • GraphQLSubscriptionConfig 类型参考
  • CacheConfig 类型参考
  • SelectorStoreUpdater 类型参考
  • Disposable 类型参考
  • GraphQL subscriptions 引导教程
  • 实现源码:requestSubscription.js、useSubscription.js
  • 测试用例:requestSubscription-test.js

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询