`<WithLocks>` 实战指南:在 react-admin 中通过 LocksContext 实时展示记录锁状态
2026/9/21 22:54:02 网站建设 项目流程
  • 前端
  • UI组件

【免费下载链接】react-admin

A 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
点击查看免费下载

<WithLocks>是 react-admin 实时协作(realtime / locks)能力中的一个声明式容器组件:它在挂载时调用dataProvider.getLocks()拉取当前资源的全部锁,将结果放入LocksContext,并订阅该资源的锁主题,收到新事件后自动重新获取锁,从而让锁状态在多个用户之间实时保持同步。本文基于本仓库的 WithLocks 文档,结合 Realtime 文档、Realtime Data Provider 文档 与配套 Hook 文档,从工作原理、安装配置、完整用法到数据结构逐层展开,读完即可在列表、详情、编辑等场景中正确使用锁信息来防止并发编辑冲突。

为什么需要<WithLocks>:协作场景下的"锁"问题

当多个用户并行处理同一批数据时(例如客服团队共同处理工单),会出现两类典型问题:

  1. 数据覆盖:两人同时编辑同一条记录,后保存者覆盖先保存者的修改;
  2. 信息滞后:用户看到的界面无法及时反映"这条记录此刻正被谁编辑"。

react-admin 的实时特性(ra-realtime,Enterprise Edition 的一部分)通过锁(Lock)机制解决这些问题。正如 Realtime 文档 所描述的:

用户可以主动请求锁,或通过编辑资源获得锁。当资源被锁定时,其他用户无法编辑它;锁释放后,其他用户才能再次编辑。

锁机制依赖 data provider 提供lockunlockgetLockgetLocks四个方法(详见下文"data provider 要求")。而<WithLocks>正是把"获取一组锁并保持实时更新"这件事封装成了开箱即用的组件,让你无需自己管理订阅生命周期,就能在任意位置读取当前资源的所有锁。

工作原理:挂载即取锁,订阅即刷新

根据 WithLocks 文档,<WithLocks>的行为可以用三步概括:

  1. 挂载时取锁:调用dataProvider.getLocks()获取当前资源的所有锁;
  2. 订阅锁主题:订阅当前资源对应的 locks 主题;
  3. 事件驱动刷新:每当收到新事件,就重新获取一次锁,保证LocksContext中的数据始终是最新的。

其中 "locks 主题" 与数据提供者的事件约定相关。在 RealtimeDataProvider.md 的 Topic 与事件格式 一节中说明:ra-realtime的 CRUD 组件订阅resource/[name]resource/[name]/[id]形式的主题,事件是包含typepayload字段的 JavaScript 对象,例如{ type: 'created', payload: { ids: [id] } }。锁数据的实时刷新正是建立在这套 Pub/Sub 机制之上:后端(或服务端业务逻辑)在锁被创建/释放时向对应主题发布事件,<WithLocks>收到后重新拉取锁列表。

注意<WithLocks>是 Enterprise Edition 组件,托管在私有 npm registry 中,使用需要有效的 Enterprise Edition 订阅(见 WithLocks 文档)。在本仓库的 Open Source 代码中搜索不到getLocks的 ra-core 实现,符合该能力由闭源@react-admin/ra-core-ee包提供的定位。

安装与前提条件

安装

<WithLocks>@react-admin/ra-core-ee包导出,按 WithLocks 文档 的说明安装:

npm install --save @react-admin/ra-core-ee # 或者使用 yarn yarn add @react-admin/ra-core-ee

更完整的实时特性(包括实时列表、实时通知、锁的各类 Hook)由@react-admin/ra-realtime包提供,安装方式一致:

npm install --save @react-admin/ra-realtime # or yarn add @react-admin/ra-realtime

这两个包都属于 React-Admin Enterprise Edition,托管在私有 npm registry,需要订阅 Enterprise Edition 计划才能安装(见 Realtime 文档的安装章节)。

data provider 要求:必须实现锁方法

<WithLocks>依赖dataProvider.getLocks()。要支持锁特性,data provider 除了 CRUD 方法外,还必须额外实现 4 个锁方法(DataProviderLive.md 与 RealtimeDataProvider.md 均有记载):

  • dataProvider.lock(resource, { id, identity, meta })
  • dataProvider.unlock(resource, { id, identity, meta })
  • dataProvider.getLock(resource, { id, meta })
  • dataProvider.getLocks(resource, { meta })

其中getLocks用于获取某个资源的全部锁,正是<WithLocks>在挂载时调用的方法。这些方法应返回在实时总线确认操作后 resolve 的 Promise。

若你的后端是 Supabase、API Platform 或 Mercure,ra-realtime提供addRealTimeMethodsBasedOnSupabaseaddRealTimeMethodsBasedOnApiPlatformaddRealTimeMethodsBasedOnMercure等增强函数,可以直接给普通 data provider 附加实时与锁能力;其他传输方式(WebSocket、长轮询、GraphQL Subscription 等)则需要自己实现这些方法(参考 RealtimeDataProvider.md 的 Custom Adapter 示例)。

另外,ra-realtime还提供addLocksMethodsBasedOnALockResource,将锁操作映射到普通 CRUD:getLocks变为getList('locks')lock变为create('locks'),适合用一张独立的locks表来存储锁(见 RealtimeDataProvider.md 的 Locks Based On A Lock Resource 一节)。

基本用法:把<WithLocks>包在列表外面

WithLocks 文档 给出的核心用法是:用<WithLocks>包裹整个列表,然后在列表内部通过useLocksContext()读取锁,并根据锁状态自定义渲染。

import { ListBase, useRecordContext } from 'ra-core'; import { WithLocks, useLocksContext } from '@react-admin/ra-core-ee'; import { DataTable } from 'your-ra-ui-library'; const LockField = () => { const locks = useLocksContext(); const record = useRecordContext(); if (!record) return null; const lock = locks.find(lock => lock.recordId === record?.id); if (!lock) return null; return <span>Locked by {lock.identity}</span>; }; const PostList = () => ( <WithLocks> <ListBase> <DataTable> {/* 其他列 */} <DataTable.Col source="lockStatus" field={LockField} /> </DataTable> </ListBase> </WithLocks> );

要点拆解:

  • useLocksContext():读取<WithLocks>提供的锁数组(Lock[])。调用位置必须在<WithLocks>的子树内;
  • useRecordContext():获取当前行(记录),lock.recordId === record.id用于判断当前记录是否被锁定;
  • lock.identity:加锁者的身份标识,可以直接展示在界面上(例如"Locked by julien");
  • 锁不存在时返回null:这样未锁定的记录不会渲染任何锁提示,界面保持干净。

Props 一览

<WithLocks>的 props 非常精简,只有一项(WithLocks 文档):

PropRequiredTypeDefaultDescription
childrenRequiredReactNodeLocksContext内部渲染的组件

也就是说,你只需关心"把哪些内容包进锁上下文",锁的获取、订阅与刷新全部由组件内部完成。

LocksContext 中的数据形态:Lock 对象长什么样

useLocksContext()返回的是一个Lock[]数组。单个锁对象的标准格式在 RealtimeDataProvider.md 的 Lock Format 一节 中有明确定义:

{ resource: 'posts', recordId: 123, identity: 'julien', createdAt: '2023-01-02T21:36:35.133Z', }

字段说明:

  • resource:资源名(如'posts');
  • recordId:被锁定记录的 id(如123);
  • identity:加锁者身份,字符串或数字均可,例如用户名、id 或认证 token;
  • createdAt:锁的获取时间(ISO 时间字符串)。

dataProvider.getLock()dataProvider.getLocks()应返回符合上述结构的锁对象/锁数组。而加锁、解锁方法(lockunlock)的参数约定为:

  • resource:资源名(如'posts');
  • params对象,包含:
    • id:记录 id(如123);
    • identity:加锁者标识(字符串或数字,例如'julien',也可以是认证 token);
    • meta:透传给 data provider 的附加信息(可选)。

如果使用addLocksMethodsBasedOnALockResourcelocks资源方案,后端锁记录还需要id字段,完整结构参见 RealtimeDataProvider.md:

{ "id": 123, "identity": "Toad", "resource": "people", "recordId": 18, "createdAt": "2020-09-29 10:20" }

进阶实战一:在数据表格中禁用编辑与删除按钮

<WithLocks>最常见的应用场景是"锁定的记录不允许他人操作"。把锁数据从上下文中取出后,可以像 useGetLocks 文档 中的示例那样,将编辑、删除按钮置灰:

const MyPostGrid = () => { const resource = useResourceContext(); const { data: locks } = useGetLocks(resource); return ( <DataTable bulkActionButtons={false}> <DataTable.Col label="Title"> <MyPostTitle locks={locks} /> </DataTable.Col> <DataTable.Col label="Actions" align="right"> <MyPostActions locks={locks} /> </DataTable.Col> </DataTable> ); }; const MyPostTitle = ({ locks }: { locks: Lock[] }) => { const record = useRecordContext(); const lock = locks.find(l => l.recordId === record.id); return ( <> <TextField source="title" /> {lock && ( <span style={{ color: 'red' }}> {` (Locked by ${lock.identity})`} </span> )} </> ); }; const MyPostActions = ({ locks }: { locks: Lock[] }) => { const record = useRecordContext(); const locked = locks.find(l => l.recordId === record.id); return ( <> <DeleteButton disabled={!!locked} /> <LockableEditButton disabled={!!locked} /> </> ); };

这套逻辑与<WithLocks>的用法完全同构——差别只在于数据来源是useLocksContext()还是useGetLocks()。若希望"取锁 + 实时订阅 + 重新拉取"由组件统一管理,就用<WithLocks>;若需要在更细粒度处单独控制数据流,则使用useGetLocks/useGetLocksLive等 Hook(详见下文"配套的锁 Hook 家族")。

进阶实战二:列表展示锁状态 + 详情页自动加锁

<WithLocks>负责"读"锁,而锁的"写"(加锁/解锁)由配套 Hook 完成。一个完整的防并发编辑方案通常这样组合:

1. 列表页:用<WithLocks>实时展示每条记录的锁定状态(如上面的LockField),让用户一眼看出"这条正被谁编辑"。

2. 编辑页:用<LockOnMount>useLockOnMount的组件形态)在挂载时锁定当前记录、卸载时自动解锁:

import { Edit, SimpleForm, TextInput } from 'react-admin'; import { LockOnMount } from '@react-admin/ra-realtime'; const PostEdit = () => ( <Edit> <SimpleForm> <TextInput source="title" fullWidth /> <TextInput source="headline" fullWidth multiline /> <TextInput source="author" fullWidth /> <LockOnMount /> </SimpleForm> </Edit> );

<LockOnMount>会通过authProvider.getIdentity()获取当前用户身份,并自动从上下文(或路由)猜测resourcerecordId(见 LockOnMount 文档)。注意:如果用户带着已锁定的记录直接关闭标签页/浏览器,LockOnMount会阻止导航并显示通知,直到记录被解锁。

3. 交互式加锁:也可以让用户"获得焦点时加锁",Realtime 文档 的锁示例展示了useGetLockLive+useLockOnCall的用法——先查当前锁,再判断表单是否禁用,最后在onFocus时请求加锁:

const { data: lock } = useGetLockLive('tickets', { id: record.id }); const { identity } = useGetIdentity(); const isFormDisabled = lock && lock.identity !== identity?.id; const [doLock] = useLockOnCall({ resource: 'tickets' }); // TextInput 的 onFocus 里调用 doLock(),并对输入控件传入 disabled={isFormDisabled}

这样,列表页(<WithLocks>只读视角)、编辑页(<LockOnMount>自动加锁)、表单(useLockOnCall聚焦加锁)三处协同,即可构成完整的协作编辑闭环。

配套的锁 Hook 家族

<WithLocks>是锁能力的一个组件入口,react-admin 实时体系中还有一整套锁相关 Hook(见 Realtime 文档 与 DataProviderLive.md),按职责可分为:

类别API作用
读取单条锁useGetLock调用dataProvider.getLock()获取某条记录的锁
读取单条锁(实时)useGetLockLive获取单条锁并订阅实时更新
读取全部锁useGetLocks调用dataProvider.getLocks()获取资源全部锁
读取全部锁(实时)useGetLocksLive获取全部锁并订阅实时更新
加锁 / 解锁useLockuseUnlock手动加锁、解锁
自动加锁useLockOnMountuseLockOnCall挂载时加锁 / 调用时加锁

选择建议:

  • 只需要"在列表里展示锁状态并实时刷新" → 用<WithLocks>(声明式、最省心);
  • 需要在自定义逻辑中单独管理数据流(例如在列表外部取锁) → 用useGetLocks/useGetLocksLive
  • 需要锁定/解锁动作(编辑页、聚焦加锁) → 用useLock/useUnlock/useLockOnMount/useLockOnCall

常见问题与注意事项

  1. 必须是 Enterprise Edition<WithLocks>@react-admin/ra-core-ee提供,私有 registry 托管,需要订阅 Enterprise Edition 计划才能安装使用(WithLocks 文档)。
  2. data provider 必须实现锁方法<WithLocks>依赖getLocks,若 data provider 未实现会直接报错。建议先用addRealTimeMethodsBasedOn*系列增强函数,或按 RealtimeDataProvider.md 的 Custom Adapter 示例手写subscribe/unsubscribe/publish与 4 个锁方法。
  3. 锁的实时性依赖后端发布事件<WithLocks>靠订阅锁主题 + 收到事件后重新getLocks()来刷新。如果后端没有在锁创建/释放时向对应主题发布事件,界面锁状态不会自动更新(主题与事件格式见 RealtimeDataProvider.md)。
  4. 身份标识要稳定:锁对象中的identityauthProvider.getIdentity()返回的 id 需要能对上(例如都是用户 id 或用户名),否则"判断是否本人持有的锁"会失效。
  5. 锁数据只读useLocksContext()提供的是锁的只读视图;加锁、解锁请走useLock/useUnlock/useLockOnMount/useLockOnCall,不要直接修改锁数组。

小结

<WithLocks>把"取锁—订阅—刷新"这一套复杂的实时流程收敛成一个容器组件:挂载即调用dataProvider.getLocks(),订阅当前资源的锁主题,事件到达后自动重新拉取,并将最新的Lock[]暴露给LocksContext。在列表、看板、数据表格中,配合useLocksContext()即可快速实现"展示谁锁定了哪条记录""锁定记录禁用编辑删除"等协作功能;再与useGetLockLiveuseLockOnMountuseLockOnCall等 Hook 组合,就能搭建出完整的防并发编辑方案。

相关文档与源码入口:

  • WithLocks 文档
  • Realtime 概览(Locks 章节)
  • Realtime Data Provider(锁方法签名、Lock 格式、locks 资源方案)
  • DataProviderLive.md(实时与锁的数据提供者要求)
  • useGetLocks Hook 文档
  • LockOnMount 组件文档
  • useLockOnMount Hook 文档
  • 前端
  • UI组件

【免费下载链接】react-admin

A 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
点击查看免费下载
上一篇:zyfun 跨平台视频播放器完整指南:5 分钟装好,本地、追剧、IPTV 直播一次全有
下一篇:一次搞定几十份 PDF 的批量处理:PDF 补丁丁使用攻略

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

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

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

立即咨询