ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

react-admin 实时订阅实战:深入掌握 `useSubscribeToRecord` 单记录事件订阅 Hook

react-admin 实时订阅实战:深入掌握 `useSubscribeToRecord` 单记录事件订阅 Hook react-admin 实时订阅实战深入掌握useSubscribeToRecord单记录事件订阅 Hook【免费下载链接】react-adminA frontend Framework for single-page applications on top of REST/GraphQL APIs, using TypeScript, React and Material Design项目地址: https://gitcode.com/gh_mirrors/re/react-adminuseSubscribeToRecord是 react-admin 企业版ra-realtime包中基于useSubscribe的特化 Hook用于订阅某个单一记录resource/[resource]/[recordId]topic上发布的实时事件。本文结合仓库中的官方文档与ra-realtime的底层机制完整讲解该 Hook 的用法、参数、回调语义以及它与useSubscribe、useSubscribeToRecordList、useGetOneLive的取舍帮助你在协同编辑、并发冲突提示、实时通知等场景中落地实时能力。背景ra-realtime的发布/订阅模型react-admin 为多人在线协作场景提供了实时Realtime能力允许多个用户并行工作、发布与订阅实时事件、在他人推送变更时自动刷新视图、向终端用户发送事件通知并通过锁机制防止同一资源被多人同时编辑。这些能力由react-admin/ra-realtime包提供属于 Enterprise Edition详见 docs/Realtime.md。其核心是一个发布/订阅pub/sub机制// 发布侧 const [publish] usePublish(); publish(topic, event); // 订阅侧 useSubscribe(topic, callback);ra-realtime在此基础上提供了一组高级 Hook 与组件useSubscribeToRecord就是其中之一与其并列的还有useSubscribe、useSubscribeCallback、useSubscribeToRecordList、usePublish完整列表见 docs/Realtime.md。在底层实时能力完全复用了 react-admin 的dataProvider适配器模式。要启用实时功能dataProvider需要实现三个新方法详见 docs/RealtimeDataProvider.mdsubscribe(topic, callback)unsubscribe(topic, callback)publish(topic, event)可选发布通常由服务端完成ra-realtime支持 Mercure、API Platform、Supabase、Socket.IO、Ably 等多种实时基础设施也可以基于本地变量手写自定义适配器仓库文档给出了基于内存数组的subscribe/unsubscribe/publish参考实现参见 docs/RealtimeDataProvider.md 的 Writing a Custom Adapter 一节。安装与前置条件ra-realtime是 React-Admin Enterprise Edition 的一部分托管在私有 npm registry 中需要订阅 Enterprise Edition 计划才能安装。按照 docs/Realtime.md 的说明安装npm install --save react-admin/ra-realtime # 或 yarn add react-admin/ra-realtime同时需要配置一个支持实时订阅的dataProvider具体要求见 docs/RealtimeDataProvider.md。基本用法订阅单记录事件useSubscribeToRecord与通用版useSubscribe的最大区别在于它只需要传一个回调函数资源名resource和记录 id 都会从当前上下文自动推断。回调会在resource/[resource]/[recordId]topic 上发布事件时被执行。以下示例实现了一个典型的并发编辑冲突提示组件当记录被他人更新收到edited事件时如果当前用户正在编辑表单isDirty就弹出一个对话框提醒冲突并允许用户选择保留自己的修改还是拉取对方的最新数据如果表单干净则直接refetch()刷新数据原文档 docs/useSubscribeToRecord.md 的完整示例import { useState } from react; import { useEditContext, useFormContext } from react-admin; import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, } from mui/material; import { useSubscribeToRecord } from react-admin/ra-realtime; const WarnWhenUpdatedBySomeoneElse () { const [open, setOpen] useState(false); const [author, setAuthor] useStatestring | null(null); const handleClose () { setOpen(false); }; const { refetch } useEditContext(); const refresh () { refetch(); handleClose(); }; const { formState: { isDirty }, } useFormContext(); useSubscribeToRecord((event: Event) { if (event.type edited) { if (isDirty) { setOpen(true); setAuthor(event.payload.user); } else { refetch(); } } }); return ( Dialog open{open} onClose{handleClose} aria-labelledbyalert-dialog-title aria-describedbyalert-dialog-description DialogTitle idalert-dialog-title Post Updated by {author} /DialogTitle DialogContent DialogContentText idalert-dialog-description Your changes and their changes may conflict. What do you want to do? /DialogContentText /DialogContent DialogActions Button onClick{handleClose}Keep my changes/Button Button onClick{refresh} Get their changes (and lose mine) /Button /DialogActions /Dialog ); }; const PostEdit () ( Edit SimpleForm TextInput sourceid disabled / TextInput sourcetitle / TextInput sourcebody multiline / WarnWhenUpdatedBySomeoneElse / /SimpleForm /Edit );上下文推断与组件摆放位置useSubscribeToRecord会分别从ResourceContext和RecordContext读取当前的资源名与记录 id。上面的例子中当应用收到resource/books/123topic 上的事件时会触发通知——其中books来自ResourceContext123来自RecordContext。一个容易踩坑的细节是组件摆放位置Show、Edit等页面组件会创建RecordContext因此useSubscribeToRecord必须放在其子组件中才能读取到上下文而不能放在页面组件本身。原文档明确指出In the example above,Showcreates theRecordContext— thats why theuseSubscribeToRecordhook is used in its child component instead of in theBookShowcomponent.与useSubscribe一样当组件卸载时useSubscribeToRecord会自动从 topic 退订无需手动清理。显式指定 resource 与 recordId如果你不在ResourceContext/RecordContext的覆盖范围内例如在自定义页面、dashboard 或弹窗中可以显式传入资源名和记录 iduseSubscribeToRecord(event { /* ... */ }, posts, 123);这一行等价于订阅resource/posts/123topic。Tip如果你的目的仅仅是“保持记录数据最新”应优先使用useGetOneLive这个实时数据 Hook而不是自己处理事件后手动refetch前者会帮你完成订阅、事件处理与数据更新整个闭环见 docs/useGetOneLive.md。参数一览useSubscribeToRecord的签名如下useSubscribeToRecord(callback, resource?, recordId?, options?)PropRequiredTypeDefaultDescriptioncallbackRequiredfunction-The callback to execute when an event is received.resourceOptionalstring-The resource to subscribe to. Defaults to the resource in theResourceContext.recordIdOptionalstring-The record id to subscribe to. Defaults to the id of the record in theRecordContext.optionsOptionalobject-The subscription options.callback事件处理回调每当resource/[resource]/[recordId]topic 上有事件发布时第一个参数传入的回调会被调用事件对象作为其参数const [open, setOpen] useState(false); const [author, setAuthor] useStatestring | null(null); const { refetch } useEditContext(); const { formState: { isDirty }, } useFormContext(); useSubscribeToRecord((event: Event) { if (event.type edited) { if (isDirty) { setOpen(true); setAuthor(event.payload.user); } else { refetch(); } } });用useCallback记忆化回调订阅/退订的开销与回调引用的变化频率直接相关。每次渲染若传入新的内联函数都会触发一次订阅再退订。原文档建议使用useCallback记忆化回调把依赖项显式列出从而避免不必要的订阅/退订抖动const [open, setOpen] useState(false); const [author, setAuthor] useStatestring | null(null); const { refetch } useEditContext(); const { formState: { isDirty }, } useFormContext(); const handleEvent useCallback( (event: Event) { if (event.type edited) { if (isDirty) { setOpen(true); setAuthor(event.payload.user); } else { refetch(); } } }, [isDirty, refetch, setOpen, setAuthor] ); useSubscribeToRecord(handleEvent);回调的第二个参数unsubscribe与useSubscribe一致回调函数的第二个参数是一个unsubscribe函数。当需要“收到某个特定事件后就停止监听”时可以在回调内主动调用它——例如在记录被删除后立刻退订避免后续再处理该记录的任何事件useSubscribeToRecord((event: Event, unsubscribe) { if (event.type deleted) { // do something unsubscribe(); } if (event.type edited) { if (isDirty) { setOpen(true); setAuthor(event.payload.user); } else { refetch(); } } });options订阅行为控制options对象支持以下属性用于精细控制订阅/退订行为enabled是否订阅默认true。设为false可延迟订阅例如在异步拿到 record id 之前先不订阅once收到第一个事件后是否自动退订默认false。适合“等待某个一次性事件”如任务完成通知unsubscribeOnUnmount组件卸载时是否退订默认true。这些选项与useSubscribe完全相同更多细节与示例如once: true的一次性订阅演示参见 docs/useSubscribe.md。recordId覆盖订阅的记录 id默认情况下useSubscribeToRecord使用RecordContext中记录的 id 来构建订阅 topic。你也可以通过第三个参数显式覆盖// 将订阅 resource/posts/123 topic useSubscribeToRecord(event { /* ... */ }, posts, 123);一个值得注意的边界情况如果传入 null 作为 record idHook 不会订阅任何 topic。这在你尚未确定记录 id例如列表页等待选中行时非常有用可以安全地占位。resource覆盖订阅的资源名同理默认使用ResourceContext中的资源名来构建 topic可通过第二个参数显式覆盖// 将订阅 resource/posts/123 topic useSubscribeToRecord(event { /* ... */ }, posts, 123);另一个边界情况如果传入空字符串作为资源名Hook 同样不会订阅任何 topic。底层原理topic 与事件格式useSubscribeToRecord之所以能“猜”出订阅目标是因为ra-realtime对 CRUD 场景有一套约定的 topic 命名规范详见 docs/RealtimeDataProvider.md 的 Topic And Event Format 一节记录级 topicresource/[resource]/[id]—— 正是useSubscribeToRecord订阅的列表级 topicresource/[resource]—— 由useSubscribeToRecordList订阅见 docs/useSubscribeToRecordList.md。事件对象是带type和payload两个字段的普通 JavaScript 对象。对于 CRUD 操作ra-realtime约定使用created、updated、deleted三种事件类型例如记录更新时后端会同时向两个 topic 发布事件{ topic: resource/${resource}/id, event: { type: updated, payload: { ids: [id] }, }, } { topic: resource/${resource}, event: { type: updated, payload: { ids: [id] }, }, }记录创建时仅向列表 topic 发布created事件删除时则同时向记录 topic 与列表 topic 发布deleted事件事件格式详见 docs/RealtimeDataProvider.md 的 CRUD Events 一节。理解了这套约定你就能在自己的回调中针对event.type精准分流处理。与其他 Hook 的选型对比在ra-realtime的 Hook 家族中docs/Realtime.md 有完整列表useSubscribeToRecord的定位与取舍如下Hook订阅 topic适用场景useSubscribe任意 topic字符串通用消息、聊天频道、自定义 topic见 docs/useSubscribe.mduseSubscribeToRecordresource/[resource]/[id]关注单条记录的事件如并发编辑冲突提示useSubscribeToRecordListresource/[resource]关注整个资源列表的事件如有人新建/更新/删除了记录的通知与刷新见 docs/useSubscribeToRecordList.mduseGetOneLive内部封装只想让单条记录数据保持最新不需要自己处理事件选型建议如果只需要监听单个记录的变更并联动 UI弹窗、通知、局部刷新useSubscribeToRecord是最直接的选择如果数据只是“要最新”优先useGetOneLive如果是列表场景则看useSubscribeToRecordList或ListLiveUpdate组件。小结useSubscribeToRecord以最小的心智负担把“单记录实时事件监听”接入 react-admin无需手写 topic、无需手动订阅/退订仅凭ResourceContext与RecordContext即可完成全部推断配合useCallback记忆化、unsubscribe第二参数以及enabled/once/unsubscribeOnUnmount选项可以构建出从冲突提示、实时通知到一次性事件等待等丰富的协作型交互。掌握它的关键在于理解底层的 topic 约定resource/[resource]/[id]与 CRUD 事件格式created/updated/deleted两者共同构成了 react-admin 实时协作体系的基石。更多配套能力实时数据 Hook、锁机制、菜单徽标等可继续查阅 docs/Realtime.md 与 docs/RealtimeDataProvider.md。【免费下载链接】react-adminA frontend Framework for single-page applications on top of REST/GraphQL APIs, using TypeScript, React and Material Design项目地址: https://gitcode.com/gh_mirrors/re/react-admin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表