react-admin 关系字段完全指南:Reference 组件体系与 dataProvider 关系数据获取
前端UI组件【免费下载链接】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点击查看免费下载React-admin 作为构建在 REST/GraphQL API 之上的前端框架其关系数据处理能力贯穿于Reference*系列字段组件之中。本文以官方文档 FieldsForRelationships 为骨架结合 ra-core 与 ra-ui-materialui 的源码实现系统讲解 one-to-many、many-to-one、one-to-one、many-to-many 四类关系在 react-admin 中的建模与呈现方式帮助读者掌握ReferenceManyField、ReferenceField、ReferenceArrayField、ReferenceManyToManyField、ReferenceOneField与ArrayField的选择与底层数据获取原理。关系数据处理的设计思想React-admin 提供了大量称为Reference 组件的组件来处理记录之间的关系。事实上react-admin 和dataProvider接口在设计上就是为了方便实现关系功能例如展示某篇文章post关联的所有评论comments展示某篇文章的作者author为某篇文章选择作者为某篇文章添加标签一个关键的设计前提是react-admin 处理关系的能力与 API 本身能否管理关系无关。只要你能为 API 提供dataProvider所有关系功能都可以正常工作。关系数据在 API 中如何建模外键、外键数组、内嵌对象、连接表决定了你应该使用哪个 Reference 组件——react-admin 提供了多种辅助组件来获取关联记录具体取决于关系的类型以及 API 的实现方式。关系类型与组件选型速查React-admin 将关系归纳为四类每一类对应不同的数据建模方式和组件选择。One-To-Many一对多当一个记录拥有多个关联记录时称为一对多关系。例如一位作者写过多本书则authors与books构成一对多关系。获取某位作者的书可选组件取决于 API 的建模方式ReferenceManyFieldAPI 使用外键如每本书有author_id字段ReferenceArrayFieldAPI 使用外键数组如每位作者有book_ids字段ArrayFieldAPI 内嵌记录数组如每位作者有books字段Many-To-One多对一多对一关系是一对多关系的反方向如每本书有一位作者。获取一本书的作者可选ReferenceFieldAPI 使用外键如每本书有author_id字段Deep Field SourceAPI 内嵌关联记录如每本书有author字段值为一个对象其他类型的关系通常可以化简为多对一关系。One-To-One一对一一对一关系如一本书有一个book_detail是基数为 1 的一对多关系的特例。获取一本书的详情可选ReferenceOneFieldAPI 使用外键如每条book_detail有book_id字段ReferenceFieldAPI 使用反向外键如每本书有book_detail_id字段Deep Field SourceAPI 内嵌关联记录如每本书有book_detail字段值为对象Many-To-Many多对多多对多关系通常建模为两次连续的一对多关系。例如一本书由多人合著可以建模为 book 与 book_authors 的一对多关系以及 book_authors 与 authors 的一对多关系。获取某位作者的书可选ReferenceManyToManyFieldAPI 使用连接表如book_authors表同时有book_id和author_id字段ReferenceArrayFieldAPI 使用外键数组如每位作者有book_ids字段每本书有author_ids字段ArrayFieldAPI 内嵌记录数组如每位作者有books字段每本书有authors字段Deep Field Source内嵌对象的直接读取当多对一关系如书的作者以内嵌对象形式实现时不需要使用任何 Reference 字段——可以直接使用普通字段并通过复合字段名compound field name如author.first_name访问。┌──────────────────┐ │ books │ │------------------│ │ id │ │ author │ │ └ first_name │ │ └ last_name │ │ └ date_of_birth │ │ title │ │ published_at │ └──────────────────┘使用示例const BookShow () ( Show SimpleShowLayout TextField sourcetitle / DateField sourcepublished_at / FunctionField labelAuthor render{record ${record.author.first_name} ${record.author.last_name}} / DateField labelAuthor DOB sourceauthor.date_of_birth / /SimpleShowLayout /Show );这里的sourceauthor.date_of_birth就是 Deep Field Source 的核心用法字段路径可以是一个点分路径dot pathreact-admin 内部使用 lodash 的get()从记录对象中取值可在 useReferenceManyFieldController.ts 的get(record, source)中看到这一取值模式。这种方式零网络请求、零 dataProvider 调用适合 API 返回冗余denormalized数据的场景。ArrayField内嵌记录数组当一对多关系如某位作者的书以内嵌对象数组实现时使用ArrayField获取数据。┌───────────────────────────┐ │ author │ │---------------------------│ │ id │ │ first_name │ │ last_name │ │ date_of_birth │ │ books │ │ └ { title, published_at} │ │ └ { title, published_at} │ │ └ { title, published_at} │ └───────────────────────────┘使用示例const AuthorShow () ( Show SimpleShowLayout TextField sourcefirst_name / TextField sourcelast_name / DateField sourcedate_of_birth / ArrayField sourcebooks DataTable DataTable.Col sourcetitle / DataTable.Col sourcepublished_at field{DateField} / /DataTable /ArrayField /SimpleShowLayout /Show );ArrayField会为内嵌记录创建一个ListContext其实现ArrayField位于 ArrayField.tsx委托给 ra-core 的ArrayFieldBase因此可以在此上下文中使用任何依赖该上下文的组件DataTable、SimpleList、Datagrid、SingleFieldList等。由于数据已经随父记录返回ArrayField不会发起任何额外的 API 请求。ReferenceField外键关联单条记录当多对一关系如书的作者使用外键实现时用ReferenceField获取关联记录。┌──────────────┐ ┌────────────────┐ │ books │ │ authors │ │--------------│ │----------------│ │ id │ ┌───│ id │ │ author_id │╾──┘ │ first_name │ │ title │ │ last_name │ │ published_at │ │ date_of_birth │ └──────────────┘ └────────────────┘使用示例const BookShow () ( Show SimpleShowLayout TextField sourcetitle / DateField sourcepublished_at / ReferenceField labelAuthor sourceauthor_id referenceauthors FunctionField render{record record ${record.first_name} ${record.last_name}} / /ReferenceField ReferenceField labelAuthor DOB sourceauthor_id referenceauthors DateField sourcedate_of_birth / /ReferenceField /SimpleShowLayout /Show );ReferenceField使用当前record此例中的 book通过外键author_id读取引用 id然后调用dataProvider.getOne(authors, { id })获取关联作者。它还会创建一个RecordContext来存放引用记录因此任何依赖该上下文的组件TextField、SimpleShowLayout等都可以在 children 中使用。技巧无需担心在同一个表格中调用两次ReferenceField会造成重复请求——react-admin 只会发起一次 API 调用。这一点在 useReference.ts 中可以得到印证它内部通过useGetManyAggregate携带{ ids: [id] }获取数据而非直接调用getOne。列表中的 n1 问题与请求聚合仅展示单条记录还不够更多时候需要为一本书列表展示作者信息const BookList () ( List DataTable DataTable.Col sourcetitle / DataTable.Col sourcepublished_at field{DateField} / DataTable.Col sourceauthor_id labelAuthor ReferenceField sourceauthor_id referenceauthors FunctionField render{record ${record.first_name} ${record.last_name}} / /ReferenceField /DataTable.Col DataTable.Col sourceauthor_id labelAuthor DOB ReferenceField sourceauthor_id referenceauthors DateField sourcedate_of_birth / /ReferenceField /DataTable.Col /DataTable /List );如果书列表的每一行都触发一次dataProvider.getOne(authors, { id })调用当列表有大量行例如 25 行时应用会变得非常慢甚至可能因滥用请求而被 API 封禁——这就是臭名昭著的n1 问题。幸运的是ReferenceField会聚合并去重页面中的所有渲染生成一个优化的请求。在上述示例中书列表不会发起 n 次getOne调用而是只发起一次dataProvider.getMany(authors, { ids })调用。其底层实现位于 useGetManyAggregate.tscallGetManyQueries函数通过batch()将同一事件循环 tick 内的所有getMany调用合并按resource meta分组用union去重合并 ids最终只调用一次dataProvider.getMany()再把返回数据按各自的 ids 过滤分发给每个等待的调用方。这既解决了 n1 问题又顺带在成功后把每条记录写入getOne缓存使后续getOne请求可以直接命中缓存。ReferenceManyField外键关联多条记录当一对多关系如某位作者的书使用外键实现时用ReferenceManyField获取关联记录。┌────────────────┐ ┌──────────────┐ │ authors │ │ books │ │----------------│ │--------------│ │ id │───┐ │ id │ │ first_name │ └──╼│ author_id │ │ last_name │ │ title │ │ date_of_birth │ │ published_at │ └────────────────┘ └──────────────┘使用示例const AuthorShow () ( Show SimpleShowLayout TextField sourcefirst_name / TextField sourcelast_name / DateField sourcedate_of_birth / ReferenceManyField referencebooks targetauthor_id DataTable DataTable.Col sourcetitle / DataTable.Col sourcepublished_at field{DateField} / /DataTable /ReferenceManyField /SimpleShowLayout /Show );ReferenceManyField使用当前record此例中的 author基于外键字段author_id构建书籍列表的过滤器然后调用dataProvider.getManyReference(books, { target: author_id, id: book.id })获取关联书籍。它会创建一个ListContext来存放关联记录因此任何依赖该上下文的组件DataTable、SimpleList、Datagrid等都可以使用。为什么用 getManyReference 而不是 getList对于许多 API 来说dataProvider.getList()和dataProvider.getManyReference()之间没有区别——后者是前者的特化版本只是预置了一个filter。但有些 API 将关联记录暴露为子路由因此需要特殊方法来获取它们。例如某位作者的书可以通过以下端点暴露GET /authors/:id/books这就是ReferenceManyField使用getManyReference()方法而非getList()的原因。从源码看useGetManyReferenceuseGetManyReference.ts的查询键为[resource, getManyReference, { target, id, pagination, sort, filter, meta }]当target缺失或id为 null 时直接 rejecttarget and id are required保证请求参数完整。常用 Props 与默认值ReferenceManyField还提供一系列可选 props其默认值定义在 ReferenceManyFieldBase.tsx 与 useReferenceManyFieldController.tsProp默认值说明reference必填关联资源名必须是Admin下注册的Resource之一target必填外键字段名如author_idsourceid当前记录中用于构建过滤器的字段filter{}附加过滤条件sort{ field: id, order: DESC }关联记录的排序perPage25默认每页展示条数page1默认页码debounce500过滤器变更的去抖毫秒数pagination无分页组件节点empty/error/loading/offline无空、错误、加载、离线状态的定制节点render无渲染函数接收ListControllerResult使用示例perPage、sort、filterReferenceManyField perPage{10} sort{{ field: created_at, order: DESC }} filter{{ is_published: true }} referencecomments targetpost_id ... /ReferenceManyField此外ReferenceManyFieldBase会额外提供ResourceContextProvidervalue 为reference和ListContextProvider并把data、total、isPending、error等状态暴露给 children配合 react-query 的placeholderData在翻页时保持旧数据占位。该控制器还内置了选择selection、排序sort、过滤filter与onSelectAll全选逻辑storeKey默认形如${resource}.${record?.id}.${reference}这意味着你可以在ReferenceManyField内直接使用带批量操作的工具条组件。ReferenceArrayField外键数组关联多条记录当一对多关系如某位作者的书使用外键数组实现时用ReferenceArrayField获取关联记录。┌────────────────┐ ┌──────────────┐ │ authors │ │ books │ │----------------│ │--------------│ │ id │ ┌───│ id │ │ first_name │ │ │ title │ │ last_name │ │ │ published_at │ │ date_of_birth │ │ └──────────────┘ │ book_ids │╾──┘ └────────────────┘使用示例const AuthorShow () ( Show SimpleShowLayout TextField sourcefirst_name / TextField sourcelast_name / DateField sourcedate_of_birth / ReferenceArrayField referencebooks sourcebook_ids DataTable DataTable.Col sourcetitle / DataTable.Col sourcepublished_at field{DateField} / /DataTable /ReferenceArrayField /SimpleShowLayout /Show );ReferenceArrayField读取当前record此例中的 author中的book_ids列表然后调用dataProvider.getMany(books, { ids })获取关联书籍。它同样创建一个ListContext来存放关联记录因此任何依赖该上下文的组件DataTable、SimpleList等都可以使用。其源码实现位于 ReferenceArrayField.tsxUI 层委托给 ra-core 的ReferenceArrayFieldBase内部控制器useReferenceArrayFieldController.ts同样是基于useGetManyAggregate实现。在列表页中的用法ReferenceArrayField也可以用在列表页中const AuthorList () ( List DataTable DataTable.Col sourcefirst_name / DataTable.Col sourcelast_name / DataTable.Col sourcedate_of_birth field{DateField} / DataTable.Col labelBooks sourcebook_ids ReferenceArrayField referencebooks sourcebook_ids SingleFieldList TextField sourcetitle / /SingleFieldList /ReferenceArrayField /DataTable.Col /DataTable /List );与ReferenceField一样ReferenceArrayField会聚合并去重页面中的所有渲染并生成优化请求。因此对于整个作者列表只会发起一次dataProvider.getMany(books, { ids })调用——这同样得益于useGetManyAggregate的批量合并机制。ReferenceArrayField的默认perPage为 1000区别于ReferenceManyField的 25默认按引用顺序即 ids 数组的顺序展示结果也可以通过sort改变顺序用filter只展示子集ReferenceArrayField perPage{10} sort{{ field: name, order: ASC }} filter{{ is_published: true }} referencecategories sourcecategory_ids ... /ReferenceArrayFieldReferenceManyToManyField连接表多对多ReferenceManyToManyField是 Enterprise EditionReact Admin Enterprise Edition字段用于展示通过连接表join table实现的多对多关系——即用两次一对多关系建模多对多。┌──────────────────┐ ┌──────────────┐ ┌───────────────┐ │ books │ │ book_authors │ │ authors │ │------------------│ │--------------│ │---------------│ │ id │───┐ │ id │ │ id │ │ title │ └──╼│ book_id │ ┌──│ first_name │ │ published_at │ │ author_id │╾──┘ │ last_name │ └──────────────────┘ │ is_public │ │ date_of_birth │ └──────────────┘ └───────────────┘展示某位作者的书const AuthorShow () ( Show SimpleShowLayout TextField sourcefirst_name / TextField sourcelast_name / DateField sourcedate_of_birth / ReferenceManyToManyField referencebooks throughbook_authors usingauthor_id,book_id DataTable DataTable.Col sourcetitle / DataTable.Col sourcepublished_at field{DateField} / /DataTable /ReferenceManyToManyField EditButton / /SimpleShowLayout /Show );展示一本书的作者const BookShow props ( Show SimpleShowLayout TextField sourcetitle / DateField sourcepublished_at / ReferenceManyToManyField referenceauthors throughbook_authors usingbook_id,author_id DataTable DataTable.Col labelAuthor render{record ${record.first_name} ${record.last_name}} / DataTable.Col sourcedate_of_birth field{DateField} / /DataTable /ReferenceManyToManyField EditButton / /SimpleShowLayout /Show );三个关键 props 的含义reference目标资源名要展示的关联资源如books或authorsthrough连接表对应的资源名如book_authorsusing连接表中关联两个资源的外键字段对格式为source_field,target_field如author_id,book_id顺序必须与当前记录到目标记录的指向一致ReferenceManyToManyField同样创建一个ListContext来存放关联记录因此任何依赖该上下文的组件DataTable、SimpleList等都可以使用。该组件位于 Enterprise Edition 的react-admin/ra-relationships包中open-source 版本不包含此组件如需此能力需要引入对应商业包本文所分析的仓库 packages 目录中同样不包含其 open-source 实现。ReferenceOneField一对一外键关联当一对一关系如书的详情使用外键实现时用ReferenceOneField获取关联记录。┌──────────────┐ ┌──────────────┐ │ books │ │ book_details │ │--------------│ │--------------│ │ id │───┐ │ id │ │ title │ └──╼│ book_id │ │ published_at │ │ genre │ └──────────────┘ │ ISBN │ └──────────────┘使用方式const BookShow () ( Show SimpleShowLayout TextField sourcetitle / DateField sourcepublished_at / ReferenceOneField labelGenre referencebook_details targetbook_id TextField sourcegenre / /ReferenceOneField ReferenceOneField labelISBN referencebook_details targetbook_id TextField sourceISBN / /ReferenceOneField /SimpleShowLayout /Show );ReferenceOneField的行为与ReferenceManyField类似它使用当前record此例中的 book基于外键book_id为 book_details 构建过滤器然后调用dataProvider.getManyReference(book_details, { target: book_id, id: book.id })获取关联详情并取第一条。其控制器源码 useReferenceOneFieldController.tsx 正是基于useGetManyReference实现。ReferenceOneField会创建一个RecordContext来存放引用记录因此任何依赖该上下文的组件TextField、SimpleShowLayout等都可以使用。从 ReferenceOneFieldBase.tsx 可以看到它还支持sort、filter、link、queryOptions等 props并通过useGetPathForRecord生成指向关联记录详情页的链接。技巧与ReferenceField一样可以在同一组件中按需多次调用ReferenceOneFieldreact-admin 只会发起一次dataProvider.getManyReference()调用相同的查询键会被 react-query 去重。对于反向的一对一关系如关联到传记的作者可以使用ReferenceField实现。关系数据请求链路小结从源码层面对比各 Reference 组件的底层数据获取方式可以帮助开发者理解不同建模下的真实请求形态组件关系方向数据建模底层 dataProvider 方法上下文ArrayField一对多内嵌记录数组无数据已内嵌ListContextReferenceField多对一外键getMany经由useGetManyAggregate聚合去重RecordContextReferenceManyField一对多外键getManyReferenceListContextReferenceArrayField一对多外键数组getMany聚合去重ListContextReferenceManyToManyFieldEE多对多连接表分步getManyReference/getManyListContextReferenceOneField一对一外键getManyReference取第一条RecordContext所有数据获取都经过dataProvider抽象层因此无论底层 API 是 REST 还是 GraphQL只要dataProvider正确实现了getOne、getMany、getManyReference等方法关系功能即可正常工作这也是 react-admin 关系体系“与 API 关系管理能力解耦”的设计精髓。相关的dataProvider方法签名可以参考 DataProviderWriting 与 DataProviderList 文档各关系的完整 API 参考可进一步阅读 ReferenceField、ReferenceManyField、ReferenceArrayField、ReferenceOneField、ReferenceManyToManyField 与 ArrayField 等独立章节。赞分享前端UI组件【免费下载链接】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点击查看免费下载相关推荐react-admin ChipField 组件完全指南用 Material UI Chip 优雅展示标签字段与一对多关系react admin ChipField 组件完全指南用 Material UI Chip 优雅展示标签字段与一对多关系 本篇技术指南以 react adm前端UI组件react-admin 一对一关系编辑组件 ReferenceOneInput 完整使用指南react admin 一对一关系编辑组件 ReferenceOneInput 完整使用指南 ReferenceOneInput 是 react admin前端UI组件Refine v5 数据获取完全指南Data Provider、数据 Hooks 与关系管理实战Refine v5 数据获取完全指南Data Provider、数据 Hooks 与关系管理实战 数据获取是所有内部工具与 Admin Panel 应用的核心前端企业应用上一篇从入门到精通Data Engineer Handbook全攻略下一篇如何用AI实现视频智能剪辑FunClip完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考