在 Refine 的 useDataGrid 中渲染关联数据:借助 useSelect 将外键列转换为可读标签的完整实战
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
导读
在 Refine 的 MUI 集成中,useDataGrid为@mui/x-data-grid的<DataGrid>组件提供了开箱即用的排序、过滤与分页能力;但当表格列中存储的是外键(如category.id)时,直接渲染只会显示一串 ID,可读性很差。本文以官方文档 FAQ「How can I handle relational data?」中的实时示例(关联数据 Live Preview 代码块)为主体,完整讲解如何用@refinedev/core的useSelect拉取关联资源、结合singleSelect列类型与renderCell把外键列渲染成分类名称,并延伸阅读useDataGrid的源码实现原理,帮助你在实际项目中快速落地「外键列显示可读标签」这一高频需求。
问题场景:外键列直接展示 ID 的困境
在典型的博客后台「文章列表」页面中,posts资源里的每条记录都带有category.id外键。如果直接把该字段作为<DataGrid>的一列:
- 用户看到的是
1、2、3这类无意义的数字,无法快速理解文章归属; - 表格也丧失了「按分类筛选」「按分类展示」的能力。
正确做法是:先通过useSelect一次性拉取categories资源的全部选项,再把「ID → 名称」的映射交给valueOptions与renderCell使用。这正是原文档(index.md 的 FAQ 章节)推荐的方案。
完整可运行示例
以下代码完整继承自仓库中的关联数据 Live Preview 片段(_partial-use-data-grid-relational-live-preview.md),它同时演示了useDataGrid的分页、排序、过滤初始值与syncWithLocation,以及useSelect的关系数据加载:
import React from "react"; import { useSelect } from "@refinedev/core"; import { useDataGrid, List } from "@refinedev/mui"; import { DataGrid, GridColDef } from "@mui/x-data-grid"; import { ICategory, IPost } from "interfaces"; const PostsList: React.FC = () => { const { dataGridProps } = useDataGrid<IPost>({ pagination: { currentPage: 2, pageSize: 10, }, sorters: { initial: [ { field: "title", order: "asc", }, ], }, filters: { initial: [ { field: "status", operator: "eq", value: "draft", }, ], }, syncWithLocation: true, }); // 1. 拉取 categories 的全部选项(关闭分页) const { options, query: { isLoading }, } = useSelect<ICategory>({ resource: "categories", pagination: { mode: "off", }, }); const columns = React.useMemo<GridColDef<IPost>[]>( () => [ { field: "id", headerName: "ID", type: "number", width: 50, }, { field: "title", headerName: "Title", minWidth: 400, flex: 1 }, // 2. 外键列:singleSelect + valueOptions + renderCell { field: "category.id", headerName: "Category", type: "singleSelect", headerAlign: "left", align: "left", minWidth: 250, flex: 0.5, valueOptions: options, display: "flex", renderCell: function render({ row }) { if (isLoading) { return "Loading..."; } const category = options.find( (item) => item.value.toString() === row.category.id.toString(), ); return category?.label; }, }, { field: "status", headerName: "Status", minWidth: 120, flex: 0.3, type: "singleSelect", valueOptions: ["draft", "published", "rejected"], }, ], [options, isLoading], ); return ( <List> <DataGrid {...dataGridProps} columns={columns} rowsPerPageOptions={[10, 20, 30, 50, 100]} /> </List> ); };提示:仓库中该 Live Preview 片段被 index.md 以
import RelationalPreview from "./_partial-use-data-grid-relational-live-preview.md"的方式嵌入 FAQ 章节,可直接在文档站点中交互预览。
逐步拆解:useDataGrid 的表格状态配置
示例前四段配置展示了useDataGrid对<DataGrid>状态管理的核心能力(这些参数在 index.md 的 Properties 章节有完整说明):
| 配置项 | 示例值 | 作用 |
|---|---|---|
pagination.currentPage | 2 | 初始页码,默认1 |
pagination.pageSize | 10 | 每页条数,默认25 |
sorters.initial | [{ field: "title", order: "asc" }] | 初始排序规则,用户改动后会被清除 |
filters.initial | [{ field: "status", operator: "eq", value: "draft" }] | 初始过滤条件,用户改动后会被清除 |
syncWithLocation | true | 将分页/排序/过滤状态编码进 URL 查询参数,可分享或收藏 |
其中filters.initial使用的operator: "eq"属于 Refine 内置过滤运算符之一,配合filters.permanent(永久过滤,不可被用户清除)与filters.defaultBehavior("merge"合并或"replace"替换,默认"merge")可以覆盖绝大多数过滤场景。
关键参数速查
pagination.mode:"off"(不分页全量拉取)、"client"(全量拉取后客户端分页)、"server"(默认,按currentPage/pageSize请求);sorters.mode/filters.mode:"server"(默认,参数发给服务端)或"off"(不发送,交给<DataGrid>客户端处理);sorters.permanent/filters.permanent:不可被用户更改的「永久」值;queryOptions:透传给底层useList的 react-query 选项,例如{ retry: 3 }。
核心技巧:用 useSelect 构建「ID → 标签」映射
关系数据的核心在于下面这段高亮代码:
const { options, query: { isLoading }, } = useSelect<ICategory>({ resource: "categories", pagination: { mode: "off", }, });要点有三:
resource: "categories"指明要加载的关联资源,useSelect内部通过 data provider 的getList方法拉取数据;pagination.mode: "off"关闭分页,一次性取回全部分类,确保下拉选项与valueOptions完整无缺;- 返回结构:
options是{ label, value }数组(label为记录标题字段,value为id),query对象上挂载了 react-query 的查询结果,其中的isLoading用于加载态渲染。
在列定义中,这一映射被两处使用:
valueOptions: options:让singleSelect列具备候选值,<DataGrid>的过滤下拉因此能直接按分类名称筛选;renderCell:遍历options,把当前行的row.category.id匹配到对应的label并渲染出来;匹配不到时(如选项尚未加载完)返回undefined,因此配合isLoading先渲染"Loading..."更稳妥。
值得注意的是renderCell内部使用了options.find(...)线性查找。当分类数量很大时,可以预先构建Map映射来优化;分类数量适中时该写法完全够用。同时,columns的useMemo依赖数组是[options, isLoading]——只有选项数据变化时才重建列定义,避免每次渲染都重建导致<DataGrid>性能下降。
深入源码:useDataGrid 是如何与 useSelect 协作的
从源码结构看,useDataGrid的实现 展示了它与useSelect天然协作的底层设计:
- 基于
useTable扩展:useDataGrid内部调用@refinedev/core的useTable(见 index.ts#L181),因此它天然继承了useTable的全部分页、排序、过滤能力;而useSelect与useTable同属@refinedev/core数据 hooks(useSelect 实现),二者都通过 data provider 的getList取数,只是返回形态不同——一个产出dataGridProps,一个产出options。 - 双向状态转换:
useDataGrid通过transformCrudFiltersToFilterModel/transformFilterModelToCrudFilters、transformCrudSortingToSortModel/transformSortModelToCrudSorting(定义于@definitions)在 Refine 的CrudFilters/CrudSorting与 MUI 的GridFilterModel/GridSortModel之间做自动转换,这也是onSortModelChange、onFilterModelChange能直接桥接<DataGrid>事件的原因。 - 服务端过滤防抖:源码在服务端过滤模式下用
DEFAULT_FILTER_DEBOUNCE_MS = 300(index.ts#L128)做输入防抖,并设置filterDebounceMs: 0关闭<DataGrid>自身的防抖,避免重复触发请求;同时applyFilters会把过滤后页码重置为1(index.ts#L250-L255)。 - 客户端模式切换:
sortingMode/filterMode会根据filtersFromProp?.mode与sortersFromProp?.mode自动判定为"server"或"client";paginationMode在分页关闭时返回"client",把分页逻辑完全交给 MUI。
因此,当你在表格里看到「Category 列既能显示名称、又能作为singleSelect过滤选项」时,本质上是:useSelect提供options→valueOptions交给<DataGrid>过滤引擎 →renderCell负责展示层翻译,三者各司其职。
实战扩展:不止于分类名称
掌握了「useSelect+singleSelect+renderCell」的组合拳后,可以轻松推广到更多关系数据场景:
- 渲染关联资源的图片:
renderCell里直接返回<img src={...} />或使用 MUI 的<Avatar>,即可把用户头像、商品缩略图渲染进表格; - 多级关系:文章 → 分类 → 分类所属分组,可连续调用两个
useSelect,在renderCell中做嵌套find; - 按关联字段做客户端过滤:配合
filters.mode: "off"与<DataGrid>的过滤功能(index.md FAQ),singleSelect列的valueOptions会直接生成可点击的过滤候选,无需服务端配合; - 本地化标签:把
useSelect的结果先映射为多语言label,再交给valueOptions,即可让过滤下拉与单元格展示同时国际化。
相关资源
- 关联数据示例本体:
_partial-use-data-grid-relational-live-preview.md - 基础用法示例:
_partial-use-data-grid-basic-usage-live-preview.md useDataGrid完整 API 文档:use-data-grid/index.mduseDataGrid源码实现:packages/mui/src/hooks/useDataGrid/index.tsuseDataGrid单元测试:packages/mui/src/hooks/useDataGrid/index.spec.tsuseSelect源码:packages/core/src/hooks/useSelect/index.ts- 可运行示例项目:
examples/table-material-ui-use-data-grid(对应 CodeSandbox 示例table-material-ui-use-data-grid)
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考