前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载本篇指南以 Relay v14 官方文档为骨架深入讲解useSubscriptionHook 的用法、配置与行为并结合react-relay与relay-runtime源码与测试揭示其在组件挂载/卸载/重渲染时的真实订阅生命周期帮助你在 React 应用中用最少的样板代码接入实时数据流。useSubscription是 React Relay 提供的用于「订阅和退订 GraphQL subscription」的 Hook。它的核心价值在于把订阅的建立、清理和响应式重订阅完全声明式地交给 React 生命周期管理开发者只需提供一个务必 memoized 的GraphQLSubscriptionConfig配置对象即可在组件挂载时自动订阅、卸载时自动退订并在环境、配置或订阅函数变化时自动重建订阅。本文将以 v14.0.0 官方 API 参考为主体结合react-relay中的 useSubscription.js 实现与relay-runtime中的 requestSubscription.js把 Hook 的参数、行为边界、底层原理与最佳实践一次讲透。1. 快速上手用useSubscription建立订阅1.1 最小可用示例首先用graphql标签定义一个 subscription注意使用subscription关键字而非query然后在组件内通过useMemo构造并 memoized 配置对象最后调用useSubscription(config)import {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 (/* ... */); }1.2 一个完整的业务示例参考 v14.0.0 GraphQL Subscriptions 指南 中的反馈点赞示例我们可以在一个自定义 Hook 中封装订阅逻辑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接收一个GraphQLSubscriptionConfig对象其中至少包含subscriptionGraphQL 字面量和variables订阅建立时的变量。useSubscription还接受一个 Flow 类型参数与 query 一样subscription 的 Flow 类型由 Relay Compiler 生成见下文“类型参数”一节。提供该类型是官方推荐的最佳实践它能让GraphQLSubscriptionConfig获得静态类型检查。与useLazyLoadQuery等 API 不同Relay不会在渲染阶段尝试建立订阅而是推迟到 effect 阶段执行。1.3 收到订阅事件后发生了什么当订阅建立后只要后端事件流如feedback_like_subscribe产生事件Relay 就会拿到对应的订阅响应。由于Feedback类型包含id字段Relay Compiler 会自动为 subscription 补选id字段响应到达后Relay 会在 store 中找到id匹配的 Feedback 对象并用新的like_count更新它。如果这些字段值因此发生变化任何选择了这些字段的组件都会自动重渲染——这正是 GraphQL 订阅驱动 UI 实时更新的核心机制。2. Arguments两个参数详解useSubscription接受两个参数参数类型说明configGraphQLSubscriptionConfigTSubscriptionPayload传递给requestSubscription的配置对象requestSubscriptionFn?TSubscriptionPayload(IEnvironment, GraphQLSubscriptionConfigTSubscriptionPayload) Disposable可选。与requestSubscription签名相同的自定义函数若传入则以它替代默认实现。默认值为requestSubscription其中requestSubscriptionFn的存在为依赖注入留了口子你可以在测试中用 mock 的订阅函数替换真实实现useSubscription的官方测试 useSubscription-test.js 正是这样做的——它 mock 了relay-runtime的requestSubscription并断言其被调用、dispose 被触发等行为。2.1 类型参数TSubscriptionPayloadTSubscriptionPayload订阅产生的 payload 类型。应传入与 subscription 对应的、由 Relay Compiler 自动生成的.graphql文件导出的 Flow 类型。例如import type {UserDataSubscription} from ./__generated__/UserDataSubscription.graphql;此时UserDataSubscription即作为useSubscription的类型参数使用让config获得静态类型约束。值得一提的是guide 中还指出参数类型如FeedbackLikeSubscribeData的名字来源于顶层 subscription 字段名即feedback_like_subscribe并且同样从生成的graphql.js文件中导出。3. 类型GraphQLSubscriptionConfigTSubscriptionPayloadGraphQLSubscriptionConfig的字段定义见 GraphQLSubscriptionConfig.md在relay-runtime的 requestSubscription.js 中也有对应的 Flow 类型声明字段是否必填类型说明subscription必填GraphQLTaggedNode用graphql模板字面量声明的 GraphQL subscriptionvariables必填变量对象传递给 subscription 的变量cacheConfig可选CacheConfig缓存配置例如force、poll等与请求执行相关的选项onCompleted可选() void订阅建立服务器确认订阅时执行的回调onError可选(error: Error) void订阅出错时执行的回调参数为错误对象onNext可选(response: ?TSubscriptionPayload) void收到新订阅数据时执行的回调updater可选SelectorStoreUpdater用于以编程方式imperatively更新 store 的函数configs可选ArrayDeclarativeMutationConfig声明式配置详见下文注意updater与configs不应同时提供。requestSubscription的实现中会发出 warningrequestSubscription: Expected only one of updater and configs to be provided见 requestSubscription.js。4. BehaviorHook 的行为契约按照官方文档useSubscription只是requestSubscription的一个薄封装thin wrapper其行为可归纳为三条挂载时订阅组件挂载后使用给定的config建立订阅卸载时退订组件卸载时自动退订变化时重建当 environment、config或requestSubscriptionFn发生变化时先退订旧的订阅再用新值重新订阅。4.1 源码级验证useSubscription的实现非常简洁useSubscription.jsconst actualRequestSubscription requestSubscriptionFn ?? requestSubscription; const environment useRelayEnvironment(); useEffect(() { const {dispose} actualRequestSubscription(environment, config); return dispose; }, [environment, config, actualRequestSubscription]);它通过useRelayEnvironment()获取当前 environment这也是依赖数组中environment的来源——当组件被放入不同的RelayEnvironmentProvider时effect 会重新执行。依赖数组[environment, config, actualRequestSubscription]直接决定了“环境、配置或订阅函数变化时重新订阅”的行为任何一项改变都会先运行上一次 effect 的清理函数即dispose退订再建立新订阅。effect 的清理函数正是requestSubscription返回的Disposable.dispose这保证了组件卸载时的自动退订。4.2 测试用例印证useSubscription的测试 useSubscription-test.js 逐一验证了上述契约组件挂载时调用requestSubscription组件卸载时调用requestSubscription(...).dispose传入的当前 environment 与 config 被原样转发当 environment 变化时先触发 dispose再用新 environment 重新调用requestSubscription订阅重建。这些测试与文档描述的“Subscribe when mounted / Unsubscribe when unmounted / Resubscribe on change”完全一致。4.3 重要警告config 必须 memoized从依赖数组可以推导出一个关键结论如果config在每次渲染时都是新对象那么每次渲染都会触发“退订 重新订阅”。源码注释明确写道N.B. this will re-subscribe every render if config or requestSubscriptionFn are not memoized. Please do not pass an object defined in-line.官方 guide 中也有对应的:::caution警告GraphQLSubscriptionConfig对象必须 memoized否则useSubscription会在每次渲染时 dispose 并重建订阅因此务必使用useMemo或把 subscription 定义提升到模块作用域、把 variables 依赖项控制好例如const config useMemo( () ({subscription, variables: {input}}), [input, subscription], );4.4 什么时候该用requestSubscription而不是 Hook如果你需要更复杂的场景例如命令式imperatively主动发起订阅不依赖组件生命周期请直接使用requestSubscriptionAPI。requestSubscription接收(environment, config)并返回Disposable完全由调用方掌控订阅时机与退订时机而useSubscription则把这一生命周期交给 React 管理。5. 行为细节补充5.1 关于client_subscription_id的注意事项内部文档FbInternalOnly区块提示useSubscription不会自动添加client_subscription_id。如果你的后端要求每条订阅具备唯一的客户端订阅标识例如用于幂等或审计你可能需要手动在config.variables.input中提供一个任意的client_subscription_id。这一点属于平台相关行为OSS 环境下是否必需取决于你的服务端实现。5.2requestSubscription的底层执行链路理解 Hook 行为后我们再看它调用的requestSubscription做了什么requestSubscription.jsgetRequest(config.subscription)获取 subscription 的请求描述并校验params.operationKind subscription否则抛出requestSubscription: Must use Subscription operation通过createOperationDescriptor(subscription, variables, cacheConfig)创建 operation descriptor若提供了configs则调用RelayDeclarativeMutationConfig.convert(...)将声明式配置转换为updater调用environment.executeSubscription({operation, updater})得到可观察对象并.subscribe({complete, error, next})complete→ 触发onCompleted服务器关闭订阅error→ 触发onErrornext→ 若存在onNext根据响应中的extensions.__relay_subscription_root_id构造 reader selector并从 storelookup出数据后回调onNext(data)返回{dispose: sub.unsubscribe}即 Hook effect 清理时调用的退订函数。可以看到useSubscription的“薄封装”背后实际经过了 operation 校验、descriptor 创建、declarative config 转换与 store 查询一整套标准流程。6. 订阅的完整工程实践来自官方指南为了让订阅在真实工程中可用还需关注以下配套实践详见 graphql-subscriptions.md6.1 用 fragment 展开保持组件数据一致与其在订阅里手动挑选字段如like_count更推荐在订阅响应中展开组件对应的 fragmentssubscription FeedbackLikeSubscription($input: FeedbackLikeSubscribeData!) { feedback_like_subscribe(data: $input) { feedback { ...FeedbackDisplay_feedback ...FeedbackDetail_feedback } } }这样每当事件流触发FeedbackDisplay与FeedbackDetail组件所选择的数据会被一次往返round trip内完整更新避免开发者手动追踪“哪些组件可能被哪些订阅影响”这种全局推理。这是 Relay 推崇的局部化数据声明的直接体现。6.2 事件回调onNext/onError/onCompleted在把数据写入 Relay store 之外你还可以在GraphQLSubscriptionConfig中声明onNext收到订阅 payload 时执行参数为订阅响应在 fragment spread 边界处截断onError订阅出错时执行参数为错误对象onCompleted服务器结束订阅时执行。6.3 声明式指令与连接更新订阅同样支持声明式 mutation 指令与deleteRecord指令。例如在订阅事件中把新建对象追加进连接或删除一个记录subscription DeletePostSubscription($input: DeletePostSubscribeData!) { delete_post_subscribe(data: $input) { deleted_post { id deleteRecord } } }6.4 用updater编程式更新 store当更新逻辑比“更新字段值”更复杂、声明式指令无法覆盖时可在GraphQLSubscriptionConfig中提供updater函数获得对 store 更新的完全控制更完整的讨论见 Imperatively modifying local data。6.5 配置网络层以支持订阅订阅通常通过 WebSocket 传输你需要配置 Network Layer 来支持它。以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-wsimport {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);说明上述网络层配置来自官方 OSS 指南。如果你的环境已有内置的订阅网络层支持则无需重复配置。7. 相关资源与进一步阅读useSubscriptionAPI 参考本文主体use-subscription.mdHook 源码实现useSubscription.js底层命令式 APIrequestSubscription.js配置类型定义GraphQLSubscriptionConfig.md官方订阅指南含网络层配置graphql-subscriptions.mdHook 行为测试useSubscription-test.js总结useSubscription用约 50 行代码的薄封装把requestSubscription的命令式订阅生命周期订阅、退订、环境/配置变化时重建转化为声明式的 React effect 行为是 React 应用中接入 GraphQL 实时订阅的最直接入口。使用它的两个黄金法则永远 memoize 你的 config能展开 fragment 就不要手挑字段。当需要完全掌控订阅时机例如命令式触发时则直接使用requestSubscriptionAPI。赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐Relay useSubscription 完全指南在 React 组件中优雅地订阅 GraphQL SubscriptionsRelay useSubscription 完全指南在 React 组件中优雅地订阅 GraphQL Subscriptions useSubscriptio前端开发工具Relay 的 useFragment Hook 实战指南在 React 中声明、读取与订阅 Fragment 数据Relay 的 useFragment Hook 实战指南在 React 中声明、读取与订阅 Fragment 数据 useFragment 是 Relay前端开发工具mise bootstrap compose用 [bootstrap.compose] 声明式管理 Docker Compose 项目生命周期mise bootstrap compose用 bootstrap.compose 声明式管理 Docker Compose 项目生命周期 mise boot开发工具CLI上一篇Caddy WAF完全指南从安装到部署保护Web应用的终极解决方案下一篇StackQL实战教程使用SQL自动化多云环境资源管理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考