Refine useDataGrid Hook 完全指南:为 MUI X DataGrid 集成分页、排序、筛选与行内编辑
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
导读
useDataGrid是 Refine v5 中面向 Material UI 生态的核心 Hook,它把 Refine 的useList数据获取能力与 MUI X<DataGrid>组件无缝桥接,让你无需手写状态同步即可获得开箱即用的服务端分页、排序、筛选与行内编辑能力。读完本文,你将掌握useDataGrid的全部配置项、返回值与边界场景处理方式,能够直接在 Refine 项目中搭建功能完整的 Material UI 数据表格页面。
概览:useDataGrid 是什么
通过useDataGrid,你可以直接拿到与 MUI X<DataGrid>组件兼容的 props,排序(sorting)、筛选(filtering)和分页(pagination)等核心功能全部开箱即用。底层数据获取基于 Refine 的useListHook,因此数据请求、缓存与失效逻辑都复用了 Refine 的数据层能力。
以下几点是使用前必须了解的特性:
- 兼容 MUI X 社区版的
<DataGrid>与商业版DataGridPro; - 该 Hook 从
@refinedev/core的useTable扩展而来,因此useTable的全部特性在useDataGrid中同样可用; - 默认从当前路由推断
resource,无需显式指定资源名。
从源码实现看,useDataGrid的核心逻辑位于 packages/mui/src/hooks/useDataGrid/index.ts,它内部调用useTableCore(来自@refinedev/core的useTable),并将 Refine 的CrudSorting/CrudFilters状态与 MUI X 的GridSortModel/GridFilterModel相互转换,最终拼装成dataGridProps返回。转换逻辑集中在 packages/mui/src/definitions/dataGrid/index.ts,下文会深入讲解。
基础用法
在最基本的用法中,useDataGrid会原样返回接口的数据,默认从 URL 读取resource:
import { List, useDataGrid } from "@refinedev/mui"; import { DataGrid, type GridColDef } from "@mui/x-data-grid"; const columns: GridColDef[] = [ { field: "id", headerName: "ID", type: "number" }, { field: "title", headerName: "Title" }, { field: "status", headerName: "Status" }, ]; export const PostsList: React.FC = () => { const { dataGridProps } = useDataGrid<IPost>(); return ( <List> <DataGrid {...dataGridProps} columns={columns} /> </List> ); };只需把dataGridProps展开到<DataGrid>上,分页、排序、筛选即可直接工作。仓库中的完整可运行示例位于 examples/table-material-ui-use-data-grid/src/pages/posts/list.tsx,其中演示了editable、syncWithLocation、初始筛选与初始排序的组合使用。
分页(Pagination)
useDataGrid通过设置与<DataGrid>兼容的paginationMode、paginationModel和onPaginationModelChange三个 props 来处理分页。启用syncWithLocation后,分页状态还会同步到 URL 查询参数中。
如果你希望在客户端完成分页,可以给useDataGrid传入pagination.mode并设为"client"。默认情况下dataGridProps已经包含分页所需的三件套,你可以像下面这样将它们显式拆分后传给<DataGrid>:
export const PostsList: React.FC = () => { const { dataGridProps } = useDataGrid(); const { // highlight-start paginationMode, paginationModel, onPaginationModelChange, // highlight-end ...restDataGridProps } = dataGridProps; return ( <List> <DataGrid columns={columns} {...restDataGridProps} // highlight-start paginationMode={paginationMode} paginationModel={paginationModel} onPaginationModelChange={onPaginationModelChange} // highlight-end /> </List> ); };从源码可以看到 packages/mui/src/hooks/useDataGrid/index.ts 中dataGridPaginationValues的实现:MUI X 的page从0开始,而 Refine 的currentPage从1开始,因此 Hook 在拼接paginationModel时做了page: currentPage - 1的换算;当分页模式为"off"时,则返回paginationMode: "client"并省略分页模型。对应的测试用例在 packages/mui/src/hooks/useDataGrid/index.spec.ts 中验证了client/server模式下的 props 输出,以及off模式下不设置paginationModel的行为。
排序(Sorting)
排序由 Hook 自动处理:它会设置sortingMode、sortModel和onSortModelChange三个与<DataGrid>兼容的 props,并同样支持通过syncWithLocation与 URL 同步。
export const PostsList: React.FC = () => { const { dataGridProps } = useDataGrid(); // highlight-start const { sortingMode, sortModel, onSortModelChange, ...restDataGridProps } = dataGridProps; // highlight-end return ( <List> <DataGrid columns={columns} {...restDataGridProps} // highlight-start sortingMode={sortingMode} sortModel={sortModel} onSortModelChange={onSortModelChange} // highlight-end /> </List> ); };在 DataGrid 外部控制排序
useDataGrid返回的setSorters函数可以接收CrudSorting类型的排序数组,因此你可以在<DataGrid>之外(例如工具栏按钮)触发排序:
import { useDataGrid, List } from "@refinedev/mui"; import { Button, ButtonGroup } from "@mui/material"; import { DataGrid, GridColDef } from "@mui/x-data-grid"; const columns: GridColDef[] = [ { field: "id", headerName: "ID", type: "number" }, { field: "title", headerName: "Title" }, { field: "status", headerName: "Status" }, ]; export const PostsList: React.FC = () => { const { dataGridProps, setSorters } = useDataGrid(); const handleSorting = (order: "asc" | "desc") => { setSorters([{ field: "title", order }]); }; return ( <List> <ButtonGroup variant="outlined"> <Button onClick={() => handleSorting("asc")}>Asc</Button> <Button onClick={() => handleSorting("desc")}>Desc</Button> </ButtonGroup> <DataGrid {...dataGridProps} columns={columns} /> </List> ); };多列排序的两种路径
MUI X 社区版一次只按一个条件对行排序;若要使用界面上的多列排序,需要升级到 Pro 计划。但多列排序可以在服务端完成——只要不显式传入sortModel:
return <DataGrid {...dataGridProps} sortModel={undefined} />;不传sortModel时,服务端支持同时按多个条件排序,代价是<DataGrid>的表头无法显示当前哪些字段处于排序状态。
从源码看,handleSortModelChange(packages/mui/src/hooks/useDataGrid/index.ts)调用transformSortModelToCrudSorting把 MUI 的GridSortModel转成 Refine 的CrudSorting,再交给setSorters;反向的transformCrudSortingToSortModel用于把 Refine 排序状态渲染回 MUI 的sortModel。两个转换函数都位于 packages/mui/src/definitions/dataGrid/index.ts,格式即{ field, sort }与{ field, order }之间的映射。
筛选(Filtering)
筛选同样由 Hook 自动处理:设置filterMode、filterModel和onFilterModelChange三个 props,并支持通过syncWithLocation与 URL 同步。
export const PostsList: React.FC = () => { const { dataGridProps } = useDataGrid(); // highlight-start const { filterMode, filterModel, onFilterModelChange, ...restDataGridProps } = dataGridProps; // highlight-end return ( <List> <DataGrid columns={columns} {...restDataGridProps} // highlight-start filterMode={filterMode} filterModel={filterModel} onFilterModelChange={onFilterModelChange} // highlight-end /> </List> ); };在 DataGrid 外部控制筛选
类似排序,你可以用返回的setFilters在组件外部设置筛选条件,例如用一个复选框控制 "draft" 状态:
import { useDataGrid, List } from "@refinedev/mui"; import { FormControlLabel, Checkbox } from "@mui/material"; import { DataGrid, GridColDef } from "@mui/x-data-grid"; const columns: GridColDef[] = [ { field: "id", headerName: "ID", type: "number" }, { field: "title", headerName: "Title" }, { field: "status", headerName: "Status" }, ]; export const PostsList: React.FC = () => { const { dataGridProps, setFilters } = useDataGrid(); const handleFilter = ( e: React.ChangeEvent<HTMLInputElement>, checked: boolean, ) => { setFilters([ { field: "status", value: checked ? "draft" : undefined, operator: "eq", }, ]); }; return ( <List> <FormControlLabel label="Filter by Draft Status" control={<Checkbox onChange={handleFilter} />} /> <DataGrid {...dataGridProps} columns={columns} /> </List> ); };多条件筛选的两种路径
与排序同理:MUI X 社区版界面一次只支持一个筛选条件,多条件界面筛选需要 Pro 计划;但服务端多条件筛选无需指定filterModel即可工作:
return <DataGrid {...dataGridProps} filterModel={undefined} />;不传filterModel时支持同时筛选多个字段,但无法在<DataGrid>表头展示当前生效的筛选条件。
读取当前筛选值:getDefaultFilter
Refine 提供了getDefaultFilter函数(实现在packages/core/src/definitions/table/index.ts中),可以用来读取某个字段当前的筛选值:
import { getDefaultFilter } from "@refinedev/core"; import { useDataGrid } from "@refinedev/mui"; const MyComponent = () => { const { filters } = useDataGrid({ filters: { initial: [ { field: "name", operator: "contains", value: "John Doe", }, ], }, }); const nameFilterValue = getDefaultFilter("name", filters, "contains"); console.log(nameFilterValue); // "John Doe" return { /** ... */ }; };服务端筛选的防抖处理
值得注意的一个实现细节:源码中定义了DEFAULT_FILTER_DEBOUNCE_MS = 300(packages/mui/src/hooks/useDataGrid/index.ts)。服务端筛选模式下,handleFilterModelChange会先更新本地筛选状态让输入即时响应,再通过 300ms 的防抖延迟发起服务端请求(同文件第 262-274 行),避免每次击键都触发网络请求;同时把filterDebounceMs设为0以禁用 MUI X 自带的防抖,防止输入被重置。
MUI 运算符与 Refine 运算符的映射
在 packages/mui/src/definitions/dataGrid/index.ts 中,Refine 实现了 MUI 运算符(equals、contains、isAnyOf、after、before等)与 RefineCrudOperators(eq、contains、in、gt、lt等)之间的双向转换,并针对列的type(number、singleSelect、string、date/dateTime等)选择最合适的运算符表达。这意味着你在<DataGrid>界面上选择的筛选条件会被精确翻译成 data provider 能理解的CrudFilters结构。
实时更新(Realtime Updates)
useDataGrid支持 Refine 的实时(Live)能力,但需要配置LiveProvider才能生效。Hook 挂载时,会以channel、resource等参数调用liveProvider的subscribe方法订阅实时事件,适合需要展示实时变化数据的场景。
与实时相关的配置项包括:
liveMode:收到相关实时事件后,决定自动更新数据("auto")还是手动处理("manual");onLiveEvent:订阅到新事件时执行的回调;liveParams:传给liveProvider.subscribe方法的额外参数。
行内编辑(Editing)
useDataGrid扩展了 MUI<DataGrid>的编辑能力。要开启列编辑,在列定义上设置editable: true:
const columns = React.useMemo<GridColDef<IPost>[]>( () => [ { field: "title", headerName: "Title", minWidth: 400, flex: 1, editable: true, }, ], [], );编辑背后的 useUpdate 集成
Refine v5 中,useDataGrid借助useUpdate直接与更新操作集成,省去了手动管理表单状态转换的复杂度,性能更好、交互模型更简洁。Hook 通过formProps暴露processRowUpdate与formLoading:
const { dataGridProps, formProps: { processRowUpdate, formLoading }, } = useDataGrid<IPost>();默认情况下,单元格编辑开始并完成后,processRowUpdate会被触发,内部调用useUpdate的mutate函数提交变更。核心流程如下(源码位于 packages/mui/src/hooks/useDataGrid/index.ts):
const processRowUpdate = async (newRow: TData, oldRow: TData) => { try { await new Promise((resolve, reject) => { mutate( { resource: resourceFromProp as string, id: newRow.id as string, values: newRow, }, { onError: (error) => { reject(error); }, onSuccess: (data) => { resolve(data); }, }, ); }); return newRow; } catch (error) { return oldRow; } };几个值得留意的细节:
- 只有
editable: true时processRowUpdate才会真正执行更新,否则直接resolve(oldRow); - 如果
identifier无法解析(例如没有配置 resource),会 reject 一个Resource is not defined错误; updateMutationOptions可以透传meta等选项给mutate,测试用例 packages/mui/src/hooks/useDataGrid/index.spec.ts 验证了meta会被正确传递到 data provider 的update方法。
配置项详解(Properties)
下面逐一说明useDataGrid的核心配置项、默认值与使用场景。完整类型定义可见 packages/mui/src/hooks/useDataGrid/index.ts 中的UseDataGridProps。
resource
resource默认从当前路由推断;存在多个同名资源时,可以用identifier作为主匹配键(data provider 方法仍使用<Refine/>组件中定义的资源name)。
useDataGrid({ resource: "categories", });dataProviderName
当项目配置了多个dataProvider时,用dataProviderName指定某个资源使用哪一个:
useDataGrid({ dataProviderName: "second-data-provider", });pagination.currentPage
设置初始页码,默认值为1:
useDataGrid({ pagination: { currentPage: 2, }, });pagination.pageSize
设置初始每页条数,默认值为25:
useDataGrid({ pagination: { pageSize: 10, }, });pagination.mode
取值"off"、"server"或"client",默认"server":
"off":禁用分页,获取全部记录;"client":客户端分页,先获取全部记录再在客户端分页;"server":服务端分页,按currentPage和pageSize请求数据。
useDataGrid({ pagination: { mode: "client", }, });sorters.initial
设置排序的初始值。initial不是永久的,用户改变排序后会被清除;如需永久生效,使用sorters.permanent:
useDataGrid({ sorters: { initial: [{ field: "name", order: "asc" }], }, });sorters.permanent
设置永久的、不可变更的排序值。用户改变排序时不会被清除;如需临时值,使用sorters.initial:
useDataGrid({ sorters: { permanent: [{ field: "name", order: "asc" }], }, });sorters.mode
取值"off"或"server",默认"server":
"off":排序值不发送到服务端,可在客户端自行排序;"server":服务端排序,按sorters值请求数据。
useDataGrid({ sorters: { mode: "server", }, });filters.initial
设置筛选的初始值。同样不是永久的,用户修改筛选后会被清除;如需永久生效,使用filters.permanent:
useDataGrid({ filters: { initial: [{ field: "name", operator: "contains", value: "Foo" }], }, });filters.permanent
设置永久的、不可变更的筛选值:
useDataGrid({ filters: { permanent: [{ field: "name", operator: "contains", value: "Foo" }], }, });filters.defaultBehavior
筛选行为可以是"merge"或"replace",默认"merge":
"merge":新筛选与已有筛选合并——同字段的新筛选替换旧筛选,不同字段的新筛选追加到已有筛选;"replace":用新筛选整体替换已有筛选。
该默认值也可以通过setFilters的第二个参数按次覆盖。注意一个实现细节:useDataGrid在把filters透传给底层useTableCore时,会强制将defaultBehavior固定为"replace"(见 packages/mui/src/hooks/useDataGrid/index.ts),以保证与 MUI DataGrid 的filterModel行为一致;而setFilters的第二个参数仍可传入"merge"或"replace"按次覆盖。
useDataGrid({ filters: { defaultBehavior: "replace", }, });filters.mode
取值"off"或"server",默认"server":
"off":筛选值不发送到服务端,可在客户端自行筛选;"server":服务端筛选,按filters值请求数据。
useDataGrid({ filters: { mode: "off", }, });syncWithLocation 与 URL 状态同步
启用syncWithLocation后,useDataGrid的状态(排序、筛选、分页)会自动编码进 URL 查询参数;当 URL 变化时,Hook 状态也会自动跟随更新。这使得表格状态可以在不同路由/页面之间共享,用户还可以通过书签或分享链接直达某个特定表格视图。默认值为false:
useDataGrid({ syncWithLocation: true, });(也可以在<Refine/>组件上全局开启该功能。)
queryOptions
useDataGrid底层通过useList获取数据,因此可以直接传入 TanStack Query 的queryOptions:
useDataGrid({ queryOptions: { retry: 3, }, });meta
meta用于向 data provider 方法传递额外信息,典型用途包括:针对特定用例定制 data provider 方法、用纯 JS 对象生成 GraphQL 查询。例如向getList传递自定义请求头:
useDataGrid({ meta: { headers: { "x-meta-data": "true" }, }, }); const myDataProvider = { //... getList: async ({ resource, pagination, sorters, filters, // highlight-next-line meta, }) => { // highlight-next-line const headers = meta?.headers ?? {}; const url = `${apiUrl}/${resource}`; //... //... // highlight-next-line const { data, headers } = await httpClient.get(`${url}`, { headers }); return { data, }; }, //... };successNotification 与 errorNotification
两者都需要NotificationProvider支持。数据获取成功或失败时,Hook 会调用open函数展示通知,你可以通过这两个 prop 自定义通知内容:
useDataGrid({ successNotification: (data, values, resource) => { return { message: `${data.title} Successfully fetched.`, description: "Success with no errors", type: "success", }; }, }); useDataGrid({ errorNotification: (data, values, resource) => { return { message: `Something went wrong when getting ${data.id}`, description: "Error", type: "error", }; }, });overtimeOptions
当请求耗时过长时,可用overtimeOptions展示加载提示。interval是毫秒级的时间间隔,onInterval是每个间隔触发的回调;Hook 返回的overtime.elapsedTime是已耗时的毫秒数,请求完成后变为undefined:
const { overtime } = useDataGrid({ //... overtimeOptions: { interval: 1000, onInterval(elapsedInterval) { console.log(elapsedInterval); }, }, }); console.log(overtime.elapsedTime); // undefined, 1000, 2000, 3000 4000, ... // 使用示例: { elapsedTime >= 4000 && <div>this takes a bit longer than expected</div>; }返回值详解(Return Values)
dataGridProps
<DataGrid>组件所需的 props,包含以下字段:
sortingMode:是否服务端排序,默认"server";sortModel:当前与<DataGrid>兼容的GridSortModel;onSortModelChange:用户排序某列时以新排序模型调用。该函数会自动把GridSortModel转换为CrudSorting并调用setSorters。需要覆盖时可以这样包装:
<DataGrid {...dataGridProps} columns={columns} onSortModelChange={(model, details) => { dataGridProps.onSortModelChange(model, details); // do something else }} />filterMode:是否服务端筛选,默认"server";filterModel:当前与<DataGrid>兼容的GridFilterModel;onFilterModelChange:用户筛选某列时以新筛选模型调用。该函数会自动把GridFilterModel转换为CrudFilters并调用setFilters。覆盖方式同理:
<DataGrid {...dataGridProps} columns={columns} onFilterModelChange={(model) => { dataGridProps.onFilterModelChange(model); // do something else }} />onStateChange:用户排序或筛选某列时以新状态调用,useDataGrid内部用它跟踪列类型(columnsTypes),以便把 MUI 运算符按列类型正确转换回 Refine 运算符。覆盖方式:
<DataGrid {...dataGridProps} columns={columns} onStateChange={(state) => { dataGridProps.onStateChange(state); // do something else }} />rows:表格展示的数据,由useList获取;rowCount:数据总数,由useList获取;loading:是否正在获取数据;pagination:分页配置值(pageSize、currentPage、setCurrentPage等)。
tableQuery
useList的完整返回结果,即 TanStack Query 的useQuery结果。
sorters / setSorters
sorters:当前排序状态(CrudSorting);setSorters:设置排序状态的函数,签名(sorters: CrudSorting) => void。
filters / setFilters
filters:当前筛选状态(CrudFilters);setFilters:设置筛选状态的函数,签名:
((filters: CrudFilters, behavior?: SetFilterBehavior) => void) & ((setter: (prevFilters: CrudFilters) => CrudFilters) => void);分页相关状态
currentPage:当前页码(分页禁用时为undefined);setCurrentPage:React.Dispatch<React.SetStateAction<number>> | undefined;pageSize:当前每页条数(分页禁用时为undefined);setPageSize:同上类型;pageCount:总页数(分页禁用时为undefined)。
createLinkForSyncWithLocation
签名(params: SyncWithLocationParams) => string,用于为syncWithLocation生成可访问的链接。
overtime
{ elapsedTime?: number },已耗时毫秒数,请求完成时变为undefined:
const { overtime } = useDataGrid(); console.log(overtime.elapsedTime); // undefined, 1000, 2000, 3000 4000, ...search
search会把接收到的参数发送给onSearch函数:(value: TSearchVariables) => Promise<void>。你传入的onSearch返回CrudFilters,随后这些筛选会被应用并重置到第一页。仓库测试用例 packages/mui/src/hooks/useDataGrid/index.spec.ts 演示了通过onSearch+search实现受控搜索的完整流程。
常见问题(FAQ)
如何处理关联数据(relational data)?
可以使用useSelect获取关联数据,再结合valueOptions与renderCell在<DataGrid>中展示。参考 examples/table-material-ui-use-data-grid/src/pages/posts/list.tsx 中的category.id列:它通过useSelect拉取categories资源,把选项传给valueOptions,并用renderCell将category.id渲染为分类名称。
如何实现客户端筛选?
设置filters.mode: "off"即可禁用服务端筛选,此时useDataGrid与 MUI<DataGrid>自身的筛选功能完全兼容:
useDataGrid({ filters: { mode: "off", }, });如何实现客户端排序?
设置sorters.mode: "off"即可禁用服务端排序,useDataGrid与 MUI<DataGrid>的排序功能完全兼容:
useDataGrid({ sorters: { mode: "off", }, });类型参数(Type Parameters)
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
TQueryFnData | query 函数返回的结果数据类型,继承BaseRecord | BaseRecord | BaseRecord |
TError | 继承HttpError的自定义错误类型 | HttpError | HttpError |
TSearchVariables | 搜索参数的类型 | {} | |
TData | select函数返回的结果数据类型,继承BaseRecord;未指定时默认取TQueryFnData的值 | BaseRecord | TQueryFnData |
典型用法:
const { dataGridProps } = useDataGrid<IPost, HttpError, IPostSearch>();完整示例
仓库中 examples/table-material-ui-use-data-grid 提供了开箱即用的完整示例项目:App.tsx中通过@refinedev/simple-rest指向https://api.fake-rest.refine.dev,注册posts资源并挂载 Material UI 主题;list.tsx中把editable、syncWithLocation、初始分页/筛选/排序组合起来,展示了useDataGrid的典型实战形态。
export const PostList: React.FC = () => { const { dataGridProps } = useDataGrid<IPost>({ editable: true, syncWithLocation: true, pagination: { currentPage: 1, pageSize: 10, }, filters: { initial: [ { field: "status", operator: "eq", value: "draft", }, ], }, sorters: { initial: [ { field: "title", order: "asc", }, ], }, }); return ( <List> <DataGrid {...dataGridProps} columns={columns} pageSizeOptions={[10, 20, 30, 50, 100]} /> </List> ); };总结
useDataGrid是 Refine v5 中打通"数据层"与"界面层"的桥梁:它把useList的取数能力、useTable的表格状态管理,以及 MUI X<DataGrid>的分页/排序/筛选/编辑交互封装为一套开箱即用的 props。掌握其配置项语义(尤其是pagination.mode、sorters.mode、filters.mode与syncWithLocation),并理解 packages/mui/src/definitions/dataGrid/index.ts 中的模型转换机制,你就能在项目中快速构建专业级的 Material UI 数据表格,同时保持 Refine 数据层的灵活性与可扩展性。
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考