前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载本文基于当前仓库relay29/relay中 useSubscription API 参考文档 编写并结合react-relay与relay-runtime的源码实现与测试用例进行纵深解读。useSubscription是 React Relay 中用于在组件生命周期内声明式建立、维持并清理 GraphQL 订阅的核心 Hook组件挂载即订阅、卸载即退订、依赖变化即自动重订。读完本文你将掌握其完整配置字段、行为语义、与底层requestSubscription的关系以及在真实应用中处理订阅事件、刷新组件数据、配置网络层的完整方案。一、useSubscription 是什么useSubscription是一个 React Hook用于订阅subscribe和退订unsubscribe一个 GraphQL 订阅。它把订阅的建立与销毁完全纳入 React 的组件生命周期管理组件挂载时自动订阅组件卸载时自动退订而当订阅配置或环境发生变化时自动退订并以新值重新订阅。从源码结构看useSubscription是requestSubscription命令式 API 的薄封装thin wrapper其核心实现在 packages/react-relay/relay-hooks/useSubscription.jsFlow 类型定义见 useSubscription.d.ts整体逻辑只有不到 30 行本质是在useEffect中调用requestSubscription并清理其返回的Disposable// 摘自 packages/react-relay/relay-hooks/useSubscription.js略作节选 const environment useRelayEnvironment(); useEffect(() { const {dispose} actualRequestSubscription(environment, config); return dispose; }, [environment, config, actualRequestSubscription]);这正是官方文档将其定位为thin wrapper的源码依据订阅的重活——包括操作描述符创建、executeSubscription执行、updater与声明式配置转换——全部由requestSubscription完成useSubscription只负责把这件事绑定到组件的生命周期上。二、基本用法useSubscription的典型用法如下完整示例来自原文档 use-subscription.mdimport {graphql, useSubscription} from react-relay; import {useMemo} from react; const subscription graphql subscription UserDataSubscription($input: InputData!) { # ... } ; function UserComponent({ id }) { // IMPORTANT: your config should be memoized. // Otherwise, useSubscription will re-render too frequently. const config useMemo(() ({ variables: {id}, subscription, }), [id, subscription]); useSubscription(config); return (/* ... */); }注意代码中的注释被原文档以IMPORTANT强调config 必须被 memoize使用useMemo包裹。这是因为useSubscription的useEffect依赖项包含config本身见上文源码中的[environment, config, actualRequestSubscription]如果每次渲染都内联新建一个配置对象就会导致退订—重订循环反复触发订阅被反复销毁重建。三、Arguments完整参数说明useSubscription接受两个参数参数类型说明configGraphQLSubscriptionConfig传递给requestSubscription的订阅配置对象requestSubscriptionFn?TSubscriptionPayload(IEnvironment, GraphQLSubscriptionConfigTSubscriptionPayload) Disposable可选。与requestSubscription同签名的自定义订阅函数若提供则用它替代默认实现默认值为requestSubscription从 useSubscription.js 源码可以看到requestSubscriptionFn的默认取值逻辑const actualRequestSubscription: RequestSubscriptionFn... requestSubscriptionFn ?? (requestSubscription as $FlowFixMe);即传入就用你的不传就用 relay-runtime 默认的requestSubscription。这一设计让useSubscription在测试环境中尤其有用——你可以注入一个 mock 的订阅函数来断言订阅与退订行为后面第六节会结合测试用例说明。requestSubscriptionFn的类型签名其签名与requestSubscription完全一致源码中定义为RequestSubscriptionFn类型type RequestSubscriptionFnTVariables, TData, TRawResponse ( environment: IEnvironment, config: GraphQLSubscriptionConfigTVariables, TData, TRawResponse, ) Disposable;四、GraphQLSubscriptionConfig 配置类型详解config的类型为GraphQLSubscriptionConfigTSubscriptionPayload完整字段定义见 GraphQLSubscriptionConfig.md字段必填类型说明subscription是GraphQLTaggedNode使用graphql模板字面量声明的 GraphQL 订阅variables是Variables传给订阅的变量cacheConfig否CacheConfig缓存/执行配置onCompleted否() void订阅建立服务器结束订阅时执行的回调onError否(Error) void出错时执行的回调接收错误对象onNext否(TSubscriptionPayload) void收到新数据时执行的回调updater否SelectorStoreUpdater命令式写入/更新 Relay store 的函数4.1cacheConfig缓存与执行配置cacheConfig的类型为CacheConfig可选字段如下force布尔值。若为true无论响应缓存状态如何都无条件发起请求poll数字。按指定的毫秒间隔轮询实现实时更新该值会传给setTimeoutliveConfigId字符串。通过调用 GraphQLLiveQuery 实现实时更新表示网关在 live query 场景下的配置metadata对象。用户自定义元数据transactionId字符串。用户提供的值用作某次操作执行的唯一 ID。4.2updater命令式更新 storeupdater是一个签名如下的函数详见 SelectorStoreUpdater.md(store: RecordSourceSelectorProxy, data) void它允许你命令式地直接对 Relay store 进行读写可以创建全新的记录也可以更新或删除已有记录从而完全掌控订阅载荷如何写入 store。4.3 声明式配置configs在 relay-runtime 的实际实现 requestSubscription.js 中GraphQLSubscriptionConfig还包含一个可选的configs字段ArrayDeclarativeMutationConfig用于以声明方式描述连接connection的增删改等更新。源码同时给出了一个很有价值的约束警告warning( !(config.updater configs), requestSubscription: Expected only one of updater and configs to be provided, );即**updater与configs只能二选一**同时提供会收到告警且最终以configs经由RelayDeclarativeMutationConfig.convert转换生成的 updater 为准。五、Behavior生命周期行为语义官方文档对useSubscription的行为给出了明确的契约结合源码可以逐一印证组件挂载时订阅useEffect首次执行时以传入的config调用requestSubscription(environment, config)获得返回的{dispose}组件卸载时退订useEffect的 cleanup 函数返回dispose组件卸载时 React 会调用它从而退订并释放资源依赖变化时退订并重订当environment、config或requestSubscriptionFn三者任一发生变化useEffect会先执行上一次的dispose再以新值重新订阅。对应源码 useSubscription.jsuseEffect(() { const {dispose} actualRequestSubscription(environment, config); return dispose; }, [environment, config, actualRequestSubscription]);其中actualRequestSubscription由requestSubscriptionFn ?? requestSubscription计算而来因此它本身也会作为依赖项参与重订判断。5.1 与 requestSubscription 的关系何时用useSubscription当订阅生命周期与组件生命周期一致时用它最省心无需手动管理退订何时直接用requestSubscription当需要更复杂的场景——例如命令式地发起订阅脱离组件生命周期、在事件回调中按需订阅时请直接使用requestSubscriptionAPI。它会返回一个Disposable调用dispose()即可清除订阅。requestSubscription的底层流程requestSubscription.js大致为用getRequest(config.subscription)解析订阅节点并校验操作类型必须是subscription否则抛出requestSubscription: Must use Subscription operation用createOperationDescriptor(subscription, variables, cacheConfig)创建操作描述符若提供了configs则通过RelayDeclarativeMutationConfig.convert(...)转换为updater调用environment.executeSubscription({operation, updater})并通过.subscribe(...)挂接complete/error/next回调返回{dispose: sub.unsubscribe}。5.2 一个重要的实现细节onNext 与__relay_subscription_root_id从 requestSubscription.js 的实现可见onNext回调并非直接把原始响应交给调用方而是先检查响应的extensions.__relay_subscription_root_id若存在则基于该 ID 构造新的 reader selector再通过environment.lookup(selector)从 store 中读出数据后回调。也就是说onNext收到的是经过 store 解析后的数据在 fragment spread 边界处截断这一点在官方指南的执行回调一节也有说明。六、从源码与测试看真实行为6.1 测试用例订阅与退订的断言仓库中的 useSubscription-test.js 完整验证了上述生命周期契约。测试的关键手法正是利用requestSubscriptionFn注入 mockconst requestSubscription jest.fn( (_passedEnv, _passedConfig) ({dispose}), );测试组件在RelayEnvironmentProvider中调用useSubscription(config)随后可以断言组件挂载后requestSubscription被调用、组件卸载后dispose被调用、config变化时先 dispose 再重新订阅。这从测试角度印证了薄封装 生命周期绑定的设计。6.2 类型安全建议提供 Flow/TS 类型参数useSubscription接受类型参数Flow 中为TVariables/TData/TRawResponseTypeScript 中为TSubscriptionPayload extends OperationType见 useSubscription.d.ts。与查询一样订阅的 Flow/TS 类型由 Relay 编译器从生成的.graphql.js文件中导出。官方指南明确指出始终提供该类型是最佳实践best practice这样GraphQLSubscriptionConfig会被静态类型检查onNext的载荷、variables的字段都会获得类型保障。七、进阶实战完整订阅工作流围绕useSubscription的完整实战方案官方指南 graphql-subscriptions.md 给出了从定义到刷新组件的全链路说明这里整合如下。7.1 定义订阅GraphQL 订阅与查询非常相似区别仅在于使用subscription关键字且顶层字段是订阅根字段subscription root fieldsubscription FeedbackLikeSubscription($input: FeedbackLikeSubscribeData!) { feedback_like_subscribe(data: $input) { feedback { like_count } } }在 Relay 中同样用graphql标签声明并支持与查询/片段相同的变量引用机制。7.2 在组件中建立订阅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); }要点梳理useSubscription接收包含subscriptionGraphQL 字面量与variables订阅变量的GraphQLSubscriptionConfig与useLazyLoadQuery等 API 不同Relay不会在渲染阶段发起订阅——订阅建立发生在 commit/effect 阶段订阅建立后每当服务器端事件发生后端会重新选取更新的数据推送给客户端由于Feedback类型包含id字段Relay 编译器会自动为其选择id字段订阅响应到达后Relay 会在 store 中找到id匹配的 feedback 记录并更新like_count数据发生变化后所有选取了这些字段的组件会自动重新渲染——这正是 Relay 订阅局部刷新的威力。注意变量类型名如FeedbackLikeSubscribeData由顶层订阅字段名派生而来此处来自feedback_like_subscribe并由编译器从生成的graphql.js文件中导出。7.3 刷新组件优先 spread 片段而非手动选字段手工选取like_count虽然可行但更推荐在订阅中spread 对应组件的 fragment这样订阅事件到来时所有依赖这些片段的组件数据会被一次往返single round trip整体更新避免开发者去全局推理哪些组件需要刷新subscription FeedbackLikeSubscription($input: FeedbackLikeSubscribeData!) { feedback_like_subscribe(data: $input) { feedback { ...FeedbackDisplay_feedback ...FeedbackDetail_feedback } } }7.4 处理回调onNext / onError / onCompleted除了把数据写入 storeGraphQLSubscriptionConfig还支持三个回调onNext收到订阅载荷时执行参数为订阅响应在 fragment spread 边界处截断onError订阅出错时执行参数为错误对象onCompleted服务器结束订阅时执行。7.5 声明式指令连接操作与删除记录声明式变更指令 与deleteRecord同样适用于订阅场景。例如当订阅事件需要从 store 删除某条记录时subscription DeletePostSubscription($input: DeletePostSubscribeData!) { delete_post_subscribe(data: $input) { deleted_post { id deleteRecord } } }当更新逻辑比改字段值更复杂、声明式指令无法覆盖时则使用updater函数命令式修改 store详见 Imperatively modifying store data。7.6 配置网络层让 subscribe 真正可用要让订阅真正跑起来必须配置 Relay 网络层处理订阅请求。GraphQL 订阅通常基于WebSocket传输官方指南给出了基于graphql-ws的示例network-layer 指南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库注意需要用Observable.from(subscribeObservable)将其 observable 转换为 Relay 的Observable类型。创建好network后将其传入 Relay Environment 即可使useSubscription的订阅请求真正发出。八、最佳实践与注意事项必须 memoize configGraphQLSubscriptionConfig对象若在渲染内联创建会导致每次渲染都触发退订—重订造成不必要的网络开销与状态抖动。请始终用useMemo依赖variables与subscription包裹优先使用类型参数提供编译生成的 Flow/TS 类型让variables、onNext载荷获得静态类型检查updater与configs二选一两者同时提供会收到warning且行为以configs转换结果为准组件级订阅用useSubscription命令式订阅用requestSubscription需要脱离组件生命周期、在任意时机按需建立订阅时直接用requestSubscription并自行保管返回的Disposableclient_subscription_iduseSubscription不会自动添加client_subscription_id如服务端需要请自行在config.variables.input中提供任意值网络层必须支持 subscribeNetwork.create需传入第二个参数subscribe函数通常基于 WebSocket否则订阅无法建立。相关资源API 参考use-subscription.md · request-subscription.md · GraphQLSubscriptionConfig.md实战指南graphql-subscriptions.md源码实现useSubscription.js · requestSubscription.js测试用例useSubscription-test.js赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐Relay 中的 useSubscription订阅与退订的声明式 Hook 完全指南Relay 中的 useSubscription订阅与退订的声明式 Hook 完全指南 useSubscription 是 react relay 提供的一个前端开发工具Haier hOn Home Assistant集成实战手册解锁海尔家电智能控制的终极指南Haier hOn Home Assistant集成实战手册解锁海尔家电智能控制的终极指南 你是否曾经想过为什么花大价钱购买的海尔智能家电却无法完全融入你的前端开发工具Relay useSubscription Hook 使用指南GraphQL 订阅的声明式生命周期管理Relay useSubscription Hook 使用指南GraphQL 订阅的声明式生命周期管理 本篇指南以 Relay v14 官方文档为骨架深入讲前端开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考