Relay 中的 GraphQL Subscriptions 实战指南:useSubscription、事件驱动更新与网络层配置

发布时间:2026/9/21 18:14:15
Relay 中的 GraphQL Subscriptions 实战指南:useSubscription、事件驱动更新与网络层配置 Relay 中的 GraphQL Subscriptions 实战指南useSubscription、事件驱动更新与网络层配置【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relay本指南以 Relay v14 文档体系中的 graphql-subscriptions 为核心系统讲解如何在数据驱动的 React 应用中通过 GraphQL subscriptions 订阅服务端事件流并借助useSubscription、requestSubscription等 API 将实时数据写入 Relay store、驱动组件重渲染。读完本文你将掌握订阅的声明方式、GraphQLSubscriptionConfig的完整配置项、事件回调、声明式指令与命令式 updater以及如何在网络层接入graphql-ws或subscriptions-transport-ws完成订阅通道的搭建。什么是 GraphQL SubscriptionGraphQL subscriptions 是一种允许客户端响应服务端事件流stream of server-side events来查询数据的机制。与普通 query 相比它最直观的区别是使用了subscription关键字而非querysubscription FeedbackLikeSubscription($input: FeedbackLikeSubscribeData!) { feedback_like_subscribe(data: $input) { feedback { like_count } } }理解这段订阅需要把握两个关键点建立订阅通道使用这段 GraphQL 建立订阅后每当feedback_like_subscribe事件流上有事件被发出应用就会被通知。feedback_like_subscribe是一个subscription root field订阅根字段也称 subscription field它在后端负责建立订阅。事件驱动查询与 mutation 类似订阅的处理分两步进行——首先服务端发生一个事件然后才执行查询。注意事件流本身可以是完全任意的它可以与所选字段毫无关系即没有任何保证保证订阅中选中的值会在两次通知之间发生变化。feedback_like_subscribe返回一个特定的 GraphQL 类型该类型暴露了我们可以响应服务端事件而查询的数据。在本例中我们查询 Feedback 对象及其更新后的like_count从而实时展示点赞数。客户端收到的一份订阅负载大致如下{ feedback_like_subscribe: { feedback: { id: feedback-id, like_count: 321 } } }在 Relay 中声明 Subscription在 Relay 中订阅同样使用graphql标签来声明const {graphql} require(react-relay); const feedbackLikeSubscription graphql subscription FeedbackLikeSubscription($input: FeedbackLikeSubscribeData!) { feedback_like_subscribe(data: $input) { feedback { like_count } } } ;与 query 和 fragment 一致订阅同样支持引用 GraphQL 变量。这里FeedbackLikeSubscribeData这个类型名派生自顶层订阅字段feedback_like_subscribe并且会从 Relay compiler 生成的graphql.js文件中导出。使用 useSubscription 建立订阅创建订阅有两种 APIuseSubscriptionHook 形式和requestSubscription命令式形式。下面是使用useSubscription的完整示例import type {Environment} from react-relay; import type {FeedbackLikeSubscribeData} from FeedbackLikeSubscription.graphql; const {graphql, useSubscription} require(react-relay); const {useMemo} require(React); function useFeedbackSubscription( input: FeedbackLikeSubscribeData, ) { const config useMemo({ subscription: graphql subscription FeedbackLikeSubscription( $input: FeedbackLikeSubscribeData! ) { feedback_like_subscribe(data: $input) { feedback { like_count } } } , variables: {input}, }, [input]) return useSubscription(config); }GraphQLSubscriptionConfig 的完整字段useSubscription接收一个GraphQLSubscriptionConfig对象其核心字段包括subscription包含订阅的 GraphQL 字面量variables用于建立订阅的变量。从 requestSubscription.js 的类型定义可以看到GraphQLSubscriptionConfig还支持以下可选字段字段类型作用subscriptionGraphQLSubscriptionTVariables, TData, TRawResponse订阅操作的 GraphQL 字面量必填variablesNoInferTVariables建立订阅所需变量必填onCompleted?() void服务端结束订阅时执行的回调onError?(error: Error) void订阅出错时执行的回调onNext?(response: ?TData) void收到订阅负载时执行的回调updater?SelectorStoreUpdaterTData命令式更新 store 的函数configs?ArrayDeclarativeMutationConfig声明式变更配置与updater二选一cacheConfig?CacheConfig缓存配置此外useSubscription还接受一个 Flow 类型参数。与 query 一样订阅的 Flow 类型从 Relay compiler 生成的文件中导出提供该类型后GraphQLSubscriptionConfig也会被静态类型检查——始终提供该类型是官方推荐的最佳实践。订阅的生命周期行为从 useSubscription.js 的源码可以看出 Hook 的完整生命周期逻辑当useFeedbackSubscription这个 Hook 挂载commit时Relay 才会建立订阅。与useLazyLoadQuery这类 API 不同Relay不会在渲染阶段render phase建立订阅而是通过useEffect在组件挂载后执行requestSubscription(environment, config)订阅建立后一旦事件发生后端会选中更新后的 Feedback 对象并取出like_count字段。由于Feedback类型包含id字段Relay compiler 会自动为其补充id的 selection收到订阅响应后Relay 会在 store 中找到id匹配的 feedback 对象并用新收到的like_count更新它如果这些值因此发生变化任何选中了这些字段的组件都会被重新渲染——通俗地说凡是依赖该数据的组件都会刷新。源码同时给出了一个强制警告useSubscription的依赖数组是[environment, config, actualRequestSubscription]也就是说传给useSubscription的GraphQLSubscriptionConfig对象必须被 memoized例如用useMemo否则每次渲染都会先 dispose 掉旧订阅再重新建立订阅造成无谓的重复订阅。源码中的注释也明确写道this will re-subscribe every render if config or requestSubscriptionFn are not memoized. Please do not pass an object defined in-line.如果 config 或 requestSubscriptionFn 未被 memoize则每次渲染都会重新订阅请勿传入内联定义的对象。useSubscription 的测试 也验证了这些行为组件挂载时调用requestSubscription卸载时调用其返回的dispose环境environment变化时先 dispose 再重新订阅。底层执行链路requestSubscription的实现展示了订阅从配置到执行的完整链路见 requestSubscription.js通过getRequest(config.subscription)获取请求定义并校验operationKind必须为subscription否则抛出requestSubscription: Must use Subscription operation用createOperationDescriptor基于订阅与变量创建操作描述符若同时提供updater与configs会触发 warning两者只能取其一提供configs时通过RelayDeclarativeMutationConfig.convert将其转换为 updater调用environment.executeSubscription({operation, updater})获得一个 RelayObservable 并订阅它将complete、error、next分别映射到onCompleted、onError、onNext返回{dispose: sub.unsubscribe}即订阅的销毁句柄。其中onNext的实现细节值得一提收到响应后Relay 会检查响应的extensions.__relay_subscription_root_id若存在则以该 id 构建 reader selector并通过environment.lookup(selector)从 store 中读出数据再传给onNext——也就是说onNext收到的响应数据在 fragment spread 边界处停止。而executeSubscription定义在 RelayModernEnvironment.js它返回一个 Observable其中每次结果都会被归一化normalize并提交到发布队列publish queue。注意 Observable 是惰性的——必须有人订阅它才会真正触发网络请求。使用 Fragment Spread 刷新组件在前面的例子中我们手动选中了like_count。选中该字段的组件会在收到更新值后被重渲染。但更推荐的做法是spread 与要刷新的组件对应的 fragment。这是因为组件选中的数据可能随时变化如果要求开发者知道所有可能获取其组件数据的订阅并持续维护它们就违背了 Relay 想要避免的全局推理global reasoning原则。例如我们可以把订阅改写为subscription FeedbackLikeSubscription($input: FeedbackLikeSubscribeData!) { feedback_like_subscribe(data: $input) { feedback { ...FeedbackDisplay_feedback ...FeedbackDetail_feedback } } }现在每当feedback_like_subscribe事件流上发生事件FeedbackDisplay和FeedbackDetail组件选中的数据都会被重新获取从而让这些组件始终保持在一致状态。Spread fragment 通常优于在订阅事件中手动 refetch 数据因为更新后的数据可以在一次往返single round trip内获取。订阅事件回调onNext、onError、onCompleted除了把更新数据写入 Relay store我们还可能希望在收到订阅负载、出现错误或服务端关闭订阅时执行回调。GraphQLSubscriptionConfig提供了三个回调字段onNext收到订阅负载时执行回调参数是订阅响应数据在 fragment spread 边界处停止与上文源码行为一致onError订阅出错时执行回调参数为发生的错误ErroronCompleted服务端结束订阅时执行。声明式指令Declarative Directives订阅同样支持 声明式 mutation 指令 以及deleteRecord指令。响应订阅事件操作连接ConnectionRelay 让你可以轻松地响应订阅事件向连接即列表中添加或移除条目。例如你可能想把一个新创建的用户追加到某个连接中。具体用法请参考 使用声明式指令 章节。响应订阅事件删除记录如果你想响应订阅事件从 store 中删除某个条目可以在被删除的 id 上添加deleteRecord指令subscription DeletePostSubscription($input: DeletePostSubscribeData!) { delete_post_subscribe(data: $input) { deleted_post { id deleteRecord } } }关于 mutation 场景下的删除可进一步参考 mutation 中删除条目 的说明。命令式修改本地数据updater 函数有时你需要的更新比单纯修改字段值更复杂声明式指令无法覆盖。此时GraphQLSubscriptionConfig的updater函数可以派上用场——它给予你对 store 更新方式的完全控制。updater的类型是SelectorStoreUpdaterTData其签名定义于 RelayStoreTypes.jsexport type SelectorStoreUpdaterin TMutationResponse ( store: RecordSourceSelectorProxy, data: ?TMutationResponse, ) void;即它接收一个绑定到特定 selector 的 store proxy以及订阅响应数据data通过 proxy 提供的 API 命令式地读写 store。完整讨论参见 命令式修改 store 数据 章节。配置网络层Network Layer订阅需要网络层支持。从源码看RelayNetwork.js 的Network.create(fetchFn, subscribe?)接受一个可选的subscribe函数当执行的操作operationKind subscription时若未提供subscribe会抛出 invariant 错误RelayNetwork: This network layer does not support Subscriptions. To use Subscriptions, provide a custom network layer.SubscribeFunction的类型定义为见 RelayNetworkTypes.jsexport type SubscribeFunction ( request: RequestParameters, variables: Variables, cacheConfig: CacheConfig, ) RelayObservableGraphQLResponse;即订阅函数接收请求参数、变量与缓存配置返回一个可产出零个或多个原始服务端响应的RelayObservable。GraphQL subscriptions 通常通过 WebSocket 通信。下面是基于graphql-ws的网络层配置示例import { ... Network, Observable } from relay-runtime; import { createClient } from graphql-ws; const wsClient createClient({ url:ws://localhost:3000, }); const subscribe (operation, variables) { return Observable.create((sink) { return wsClient.subscribe( { operationName: operation.name, query: operation.text, variables, }, sink, ); }); } const network Network.create(fetchQuery, subscribe);也可以使用较早的subscriptions-transport-ws库import { ... Network, Observable } from relay-runtime; import { SubscriptionClient } from subscriptions-transport-ws; const subscriptionClient new SubscriptionClient(ws://localhost:3000, { reconnect: true, }); const subscribe (request, variables) { const subscribeObservable subscriptionClient.request({ query: request.text, operationName: request.name, variables, }); // Important: Convert subscriptions-transport-ws observable type to Relays return Observable.from(subscribeObservable); }; const network Network.create(fetchQuery, subscribe);注意第二种方案中需要显式把subscriptions-transport-ws的 observable 类型转换为 Relay 的RelayObservable。完整配置说明可参考 网络层配置指南。小结GraphQL subscriptions 是构建实时数据驱动 React 应用的关键机制。通过本指南你可以掌握用subscription关键字声明订阅根字段、用useSubscription在组件挂载时建立订阅并牢记对 config 做 memoize、借助 fragment spread 让依赖组件自动刷新、用onNext/onError/onCompleted处理事件流生命周期、用声明式指令与updater灵活更新 store以及通过graphql-ws等库为网络层接入 WebSocket 订阅通道。配合源码层面的执行链路理解requestSubscription→environment.executeSubscription→RelayNetwork.execute的 subscribe 分支你就能在真实项目中可靠地实现点赞实时计数、评论实时列表、删除同步等典型实时场景。【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relay创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考