使用 Material React Table 构建强大的 React 表格
在现代 Web 应用中,表格是展示和管理数据最常用、最直观的方式之一。然而,构建一个功能齐全、性能优秀且用户体验良好的数据表格并非易事。仅仅是实现基础的分页、排序和过滤功能,往往就需要编写大量重复的样板代码。当需求进一步复杂化,例如需要列拖拽、列固定、行编辑、数据分组、虚拟化处理大型数据集等高级特性时,从零开始构建或依赖简单的组件库将变得异常耗时且充满挑战。
幸运的是,React 生态系统提供了许多优秀的表格库来帮助开发者。在众多选择中,Material React Table (MRT) 脱颖而出,尤其受到那些偏好或已经使用 Material UI (MUI) 作为其 UI 框架的开发者的青睐。Material React Table 不仅完美集成了 Material UI 的美学风格和组件,更在其基础上构建了一个功能极其丰富的 React Table 组件,它底层基于强大的 TanStack Table v8 (以前称为 React Table) 库,继承了其高性能和灵活性的核心优势。
本文将深入探讨 Material React Table 的各项特性,并详细介绍如何使用它来构建功能强大、高度可定制的 React 数据表格。
为什么选择 Material React Table?
在深入学习如何使用 Material React Table 之前,我们先来了解一下它相比其他表格库的优势:
- 基于 Material UI: 如果你的项目已经使用了 Material UI,那么 Material React Table 将是无缝集成的理想选择。它使用了 Material UI 的组件和主题,使得表格的外观和感觉与应用的其余部分保持一致,无需额外的样式调整。
- 基于 TanStack Table v8: MRT 利用了 TanStack Table 的无头 (headless) 架构。这意味着 TanStack Table 负责管理表格的核心逻辑(如数据处理、状态管理、插件系统),而 MRT 则专注于 UI 的渲染。这种分离带来了巨大的灵活性和性能优势,同时也让你能够利用 TanStack Table 庞大生态系统中的高级特性。
- 开箱即用的丰富功能: MRT 提供了企业级表格所需的大部分常用功能,并且可以通过简单的 prop 启用。这些功能包括但不限于:
- 分页 (Pagination)
- 排序 (Sorting)
- 过滤/搜索 (Filtering/Searching)
- 列可见性控制 (Column Visibility)
- 列顺序控制 (Column Ordering)
- 列拖拽 (Column Drag and Drop)
- 列调整大小 (Column Resizing)
- 列固定/锁定 (Column Pinning)
- 行选择 (Row Selection)
- 行编辑 (Row Editing)
- 行操作 (Row Actions, 如删除、查看等)
- 数据分组和聚合 (Grouping and Aggregation)
- 行展开 (Row Expansion)
- 虚拟化处理大型数据集 (Virtualization)
- 可定制的工具栏 (Toolbar Customization)
- 国际化 (Internationalization – i18n)
- 客户端和服务器端数据处理支持
- 高度可定制: 虽然提供了许多开箱即用的功能,但 MRT 的每个部分几乎都可以通过各种 prop、渲染函数和槽 (slots) 进行定制,从单元格的渲染到整个工具栏的布局,都能按照需求进行调整。
- 良好的文档和社区支持: MRT 拥有详细的文档,并且由于其底层是 TanStack Table,后者也拥有一个活跃的社区,可以找到很多有用的资源和解决方案。
总而言之,如果你需要一个强大、功能全面、与 Material UI 完美集成且易于使用的 React 表格库,Material React Table 绝对是一个值得考虑的首选。
开始使用 Material React Table
首先,你需要确保你的项目已经安装了 React 和 Material UI v5 或更高版本。如果还没有,请先安装它们。
然后,安装 Material React Table 及其 peer dependencies:
“`bash
npm install material-react-table react react-dom @mui/material @emotion/react @emotion/styled
或者使用 yarn
yarn add material-react-table react react-dom @mui/material @emotion/react @emotion/styled
“`
安装完成后,你就可以开始创建你的第一个 Material React Table 了。
最简示例
构建一个 Material React Table 需要两部分核心数据:
data
: 一个数组,其中每个元素代表表格的一行数据。通常是对象数组。columns
: 一个数组,定义了表格的列。数组中的每个对象描述了一列的配置,例如如何访问该列的数据、列头显示什么文本等。
让我们创建一个简单的表格来展示用户数据:
“`jsx
import React, { useMemo } from ‘react’;
import MaterialReactTable from ‘material-react-table’;
// 假设你的数据结构如下
const data = [
{
name: {
firstName: ‘张’,
lastName: ‘三’,
},
address: ‘北京市朝阳区’,
city: ‘北京’,
state: ‘北京’,
},
{
name: {
firstName: ‘李’,
lastName: ‘四’,
},
address: ‘上海市浦东新区’,
city: ‘上海’,
state: ‘上海’,
},
{
name: {
firstName: ‘王’,
lastName: ‘五’,
},
address: ‘广州市天河区’,
city: ‘广州’,
state: ‘广东’,
},
// … 更多数据
];
const ExampleTable = () => {
// 定义列,使用 useMemo 避免在每次渲染时重新创建列定义
const columns = useMemo(
() => [
{
// accessorKey 通常用于访问扁平数据对象中的键
accessorKey: ‘name.firstName’, // 访问嵌套对象属性
header: ‘名’,
// 你也可以在这里添加其他配置,例如过滤、排序等
},
{
accessorKey: ‘name.lastName’,
header: ‘姓’,
},
{
accessorKey: ‘address’,
header: ‘地址’,
// 默认情况下,MRT 会根据 accessorKey 或 header 自动创建过滤和排序
},
{
accessorKey: ‘city’,
header: ‘城市’,
},
{
accessorKey: ‘state’,
header: ‘省份’,
},
],
[] // 依赖项为空数组,表示只在组件挂载时创建一次
);
return (
);
};
export default ExampleTable;
“`
在这个例子中,我们定义了 data
数组和 columns
数组。columns
中的每个对象至少需要一个 accessorKey
或 accessorFn
来指定如何获取该列的数据,以及一个 header
来作为列头显示的文本。accessorKey
直接指定数据对象中的键(支持点号访问嵌套属性),而 accessorFn
是一个函数,接收整行数据对象作为参数,并返回该列需要显示的值,这在处理复杂数据结构或需要计算值时非常有用。
然后,我们将 columns
和 data
作为 prop 传递给 <MaterialReactTable />
组件。通过设置 enable
开头的 prop,我们轻松地启用了分页、排序和过滤等基本功能。
运行这个组件,你将看到一个带有分页、排序箭头、过滤输入框(全局和每列)的基础表格。
核心功能详解
Material React Table 提供了大量功能来满足各种需求。下面我们将详细介绍一些最常用的核心功能。
1. 分页 (Pagination)
分页对于处理大量数据至关重要,它可以将数据分割成更小的块,提高页面加载速度和用户体验。
- 启用:
enablePagination={true}
(默认开启) - 控制分页状态:
- 默认情况下,MRT 内部管理分页状态。
- 如果你需要外部控制分页状态(例如,与服务器端分页结合),可以使用
state
prop 传递pagination
对象,并监听onPaginationChange
事件来更新外部状态。 state.pagination = { pageIndex: number, pageSize: number }
onPaginationChange: (updaterFn | { pageIndex: number, pageSize: number }) => void
- 配置每页行数选项:
pageSizes={[10, 25, 50, 100]}
- 隐藏分页界面:
enableBottomToolbar={false}
或enableTopToolbar={false}
(分页通常在底部工具栏) - 控制分页显示模式:
paginationDisplayMode="pages"
(显示页码),"flat"
(只显示前后按钮),"default"
(默认)
示例:
“`jsx
// … (import MaterialReactTable and data/columns)
const ExampleTableWithPagination = () => {
const columns = useMemo(/ … /, []);
const data = useMemo(/ … /, []);
// 外部控制分页状态示例
const [pagination, setPagination] = useState({
pageIndex: 0, // 当前页码,从 0 开始
pageSize: 10, // 每页显示行数
});
// … 其他状态和数据获取逻辑 (特别是如果实现服务器端分页)
return (
);
};
“`
2. 排序 (Sorting)
排序允许用户按照某一列的值对数据进行升序或降序排列。
- 启用:
enableSorting={true}
(默认开启) - 禁用单列排序: 在列定义中设置
enableSorting: false
- 启用多列排序:
enableMultiSort={true}
- 手动排序 (服务器端):
manualSorting={true}
- 控制排序状态: 类似于分页,可以使用
state.sorting
和onSortingChange
外部控制。state.sorting = [{ id: string, desc: boolean }]
onSortingChange: (updaterFn | [{ id: string, desc: boolean }]) => void
- 自定义排序逻辑: 使用列定义中的
sortingFn
,传入一个函数来定义如何比较两个值。
示例:
“`jsx
// … (import MaterialReactTable and data/columns)
const columns = useMemo(
() => [
{
accessorKey: ‘name.firstName’,
header: ‘名’,
enableSorting: false, // 禁用此列的排序
},
{
accessorKey: ‘name.lastName’,
header: ‘姓’,
// 默认启用排序
},
// … 其他列
],
[]
);
// … 组件渲染部分
return (
);
“`
3. 过滤/搜索 (Filtering/Searching)
过滤允许用户根据特定条件缩小显示的数据范围。MRT 提供了全局过滤和列过滤。
- 启用:
enableFiltering={true}
(默认开启),它会同时启用全局过滤和列过滤。 - 禁用全局过滤:
enableGlobalFilter={false}
- 禁用列过滤:
enableColumnFilters={false}
- 禁用单列过滤: 在列定义中设置
enableFiltering: false
- 手动过滤 (服务器端):
manualFiltering={true}
- 控制过滤状态: 使用
state.columnFilters
和onColumnFiltersChange
外部控制列过滤状态。使用state.globalFilter
和onGlobalFilterChange
外部控制全局过滤状态。state.columnFilters = [{ id: string, value: any }]
onColumnFiltersChange: (updaterFn | [{ id: string, value: any }]) => void
state.globalFilter = any
onGlobalFilterChange: (updaterFn | any) => void
- 自定义过滤逻辑:
- 使用列定义中的
filterVariant
指定不同的过滤 UI (例如,’text’, ‘select’, ‘multi-select’, ‘range’ 等)。 - 使用列定义中的
filterFn
提供自定义过滤函数。 filterFns
prop 可以注册全局可用的自定义过滤函数。
- 使用列定义中的
示例:
“`jsx
// … (import MaterialReactTable and data/columns)
const columns = useMemo(
() => [
// … 其他列
{
accessorKey: ‘city’,
header: ‘城市’,
filterVariant: ‘select’, // 将此列的过滤输入框变为下拉选择框
filterSelectOptions: [‘北京’, ‘上海’, ‘广州’, ‘深圳’], // 下拉选项
},
{
accessorKey: ‘state’,
header: ‘省份’,
filterVariant: ‘multi-select’, // 多选下拉框
filterSelectOptions: [‘北京’, ‘上海’, ‘广东’],
},
],
[]
);
// … 组件渲染部分
return (
);
“`
4. 列可见性控制 (Column Visibility)
用户可以通过表格工具栏上的按钮来显示或隐藏某些列。
- 启用:
enableColumnVisibility={true}
(默认开启) - 禁用单列可见性切换: 在列定义中设置
enableColumnActions: false
(这也会禁用该列的其他操作菜单项) - 控制可见性状态: 使用
state.columnVisibility
和onColumnVisibilityChange
外部控制。state.columnVisibility = { [columnId]: boolean }
onColumnVisibilityChange: (updaterFn | { [columnId]: boolean }) => void
- 设置初始可见性: 在列定义中设置
initialState.columnVisibility = { [columnId]: false }
或state.columnVisibility
prop。
示例:
“`jsx
// … (import MaterialReactTable and data/columns)
const columns = useMemo(
() => [
{ accessorKey: ‘name.firstName’, header: ‘名’ },
{ accessorKey: ‘name.lastName’, header: ‘姓’ },
{
accessorKey: ‘address’,
header: ‘地址’,
enableHiding: false, // 用户无法隐藏此列
},
{ accessorKey: ‘city’, header: ‘城市’ },
{ accessorKey: ‘state’, header: ‘省份’ },
],
[]
);
// … 组件渲染部分
return (
);
“`
5. 列调整大小 (Column Resizing)
允许用户通过拖动列头之间的分隔线来调整列的宽度。
- 启用:
enableColumnResizing={true}
- 设置初始宽度: 在列定义中设置
size
prop (例如,size: 150
)。 - 配置调整模式:
columnResizeMode="onChange"
(拖拽时实时调整) 或"onEnd"
(释放拖拽时调整)。
示例:
“`jsx
// … (import MaterialReactTable and data/columns)
const columns = useMemo(
() => [
{ accessorKey: ‘name.firstName’, header: ‘名’, size: 100 }, // 设置初始宽度
{ accessorKey: ‘name.lastName’, header: ‘姓’, size: 100 },
{ accessorKey: ‘address’, header: ‘地址’, size: 300 }, // 地址列宽一点
{ accessorKey: ‘city’, header: ‘城市’, size: 150 },
{ accessorKey: ‘state’, header: ‘省份’, size: 150 },
],
[]
);
// … 组件渲染部分
return (
);
“`
6. 列固定/锁定 (Column Pinning)
允许用户将某些列(如 ID 列或操作列)固定在表格的左侧或右侧,使其在水平滚动时始终可见。
- 启用:
enableColumnPinning={true}
- 固定列: 在列定义中设置
pinned: 'left'
或pinned: 'right'
。 - 通过 UI 固定: 如果
enableColumnActions
开启,用户也可以通过列操作菜单固定/取消固定列。 - 控制固定状态: 使用
state.columnPinning
和onColumnPinningChange
外部控制。state.columnPinning = { left?: string[], right?: string[] }
onColumnPinningChange: (updaterFn | { left?: string[], right?: string[] }) => void
示例:
“`jsx
// … (import MaterialReactTable and data/columns)
const columns = useMemo(
() => [
{
accessorKey: ‘name.firstName’,
header: ‘名’,
pinned: ‘left’, // 将“名”列固定在左侧
},
{ accessorKey: ‘name.lastName’, header: ‘姓’ },
{ accessorKey: ‘address’, header: ‘地址’ },
{ accessorKey: ‘city’, header: ‘城市’ },
{
// 添加一个操作列并固定在右侧
id: ‘actions’, // 通常没有 accessorKey
header: ‘操作’,
size: 150,
Cell: ({ row }) => (
),
pinned: ‘right’, // 将操作列固定在右侧
},
],
[]
);
// … 组件渲染部分
return (
);
“`
7. 行选择 (Row Selection)
允许用户选择表格中的一行或多行。
- 启用:
enableRowSelection={true}
。这会在表格的第一列添加一个复选框。 - 启用多行选择:
enableMultiRowSelection={true}
(默认开启,如果enableRowSelection
为真)。 - 禁用多行选择 (单选):
enableMultiRowSelection={false}
。 - 控制选择状态: 使用
state.rowSelection
和onRowSelectionChange
外部控制。state.rowSelection = { [rowIndex]: boolean }
(一个对象,键是行的索引,值是是否选中)onRowSelectionChange: (updaterFn | { [rowIndex]: boolean }) => void
- 获取选中的行: 通过
state.rowSelection
或table.getState().rowSelection
获取状态对象,然后可以利用table.getSelectedRowModel().rows
获取选中的行对象数组。 - 禁用单行选择: 在行的数据对象中设置
disabled: true
或使用getRowCanSelect
prop。
示例:
“`jsx
import React, { useMemo, useState } from ‘react’;
import MaterialReactTable from ‘material-react-table’;
import { Box, Button } from ‘@mui/material’;
// … data and columns (ensure columns don’t have a custom first column conflicting with selection)
const ExampleTableWithSelection = () => {
const columns = useMemo(/ … /, []);
const data = useMemo(/ … /, []);
const [rowSelection, setRowSelection] = useState({}); // 外部控制选择状态
const handleShowSelected = (table) => {
const selectedRows = table.getSelectedRowModel().rows;
alert(‘Selected rows:\n’ + JSON.stringify(selectedRows.map(row => row.original.name.firstName), null, 2));
};
return (
renderTopToolbarCustomActions={({ table }) => ( // 在顶部工具栏添加一个按钮
)}
/>
);
};
“`
8. 数据分组和聚合 (Grouping and Aggregation)
允许用户根据一列或多列的值对数据进行分组,并在分组行或分组底部显示聚合数据(如计数、总和、平均值等)。
- 启用分组:
enableGrouping={true}
- 设置初始分组:
initialState={{ grouping: ['city'] }}
(按城市分组) 或通过state.grouping
prop。 - 允许用户通过 UI 分组: 如果
enableColumnDragging
开启,用户可以将列头拖拽到分组区域进行分组。 - 启用聚合:
enableRowAggregation={true}
- 定义聚合函数: 在列定义中设置
aggregationFn
。MRT 提供了一些内置的聚合函数(如 ‘count’, ‘sum’, ‘mean’, ‘median’, ‘min’, ‘max’ 等),你也可以自定义函数。 - 显示聚合结果:
- 在列定义中设置
AggregatedCell
或Footer
来渲染聚合值。 - 通常在需要聚合的列上设置
aggregationFn
和相应的渲染函数。
- 在列定义中设置
示例:
“`jsx
import React, { useMemo } from ‘react’;
import MaterialReactTable, {
// 导入内置聚合函数
// MRT_AggregationFns,
} from ‘material-react-table’;
// … data and columns
const ExampleTableWithGrouping = () => {
const columns = useMemo(
() => [
{
accessorKey: ‘state’,
header: ‘省份’,
enableGrouping: true, // 允许按此列分组
// Cell: ({ cell, column }) => { / 可选:自定义省份列的渲染 / return cell.getValue() },
// AggregatedCell: ({ cell }) => (${cell.getValue()} 项)
, // 在分组行中显示计数
},
{
accessorKey: ‘city’,
header: ‘城市’,
enableGrouping: true, // 允许按此列分组
// Cell: ({ cell, column }) => { / 可选:自定义城市列的渲染 / return cell.getValue() },
},
{
accessorKey: ‘name.firstName’,
header: ‘名’,
enableGrouping: false, // 不允许按名字分组
},
{ accessorKey: ‘name.lastName’, header: ‘姓’ },
{ accessorKey: ‘address’, header: ‘地址’ },
// 假设数据中有数字列,如 age 或 salary,可以进行聚合
// {
// accessorKey: ‘salary’,
// header: ‘薪资’,
// aggregationFn: ‘mean’, // 计算平均值
// // 你也可以使用自定义函数: aggregationFn: (columnId, leafRows, childRows) => { … }
// AggregatedCell: ({ cell, column }) => { // 在分组行中显示聚合值
// const avgSalary = cell.getValue();
// return avgSalary != null ? 平均薪资: ${avgSalary.toFixed(2)}
: ”;
// },
// Footer: ({ column }) => { // 在列底部显示总计或其他聚合
// const totalSalary = useMemo(
// () => data.reduce((sum, row) => sum + (row.salary || 0), 0),
// [data]
// );
// return
;
// }
// }
],
[]
);
return (
);
};
“`
9. 行展开 (Row Expansion)
允许在表格行下方显示额外的详情面板。
- 启用:
enableExpanding={true}
。 - 渲染详情面板: 使用
renderDetailPanel
prop,它是一个接收{ row, table }
对象的函数,返回要渲染的 React 元素。 - 控制展开状态: 使用
state.expanded
和onExpandedChange
外部控制。state.expanded = { [rowIndex]: boolean } | true
(true 表示展开所有行)onExpandedChange: (updaterFn | { [rowIndex]: boolean } | true) => void
- 启用展开全部按钮:
enableExpandAll={true}
(通常在分组或展开功能开启时使用)。
示例:
“`jsx
import React, { useMemo } from ‘react’;
import MaterialReactTable from ‘material-react-table’;
import { Box, Typography } from ‘@mui/material’;
// … data and columns
const ExampleTableWithExpansion = () => {
const columns = useMemo(/ … /, []);
const data = useMemo(/ … /, []);
return (
地址: {row.original.address}
{/ 你可以根据 row.original 显示更多详细信息 /}
)}
/>
);
};
“`
10. 自定义渲染 (Custom Rendering)
Material React Table 提供了多种方式来自定义表格的各个部分的渲染,包括单元格、列头、工具栏等。
- 自定义单元格渲染: 在列定义中使用
Cell
prop。这是一个函数,接收{ cell, column, row, table }
对象,返回要渲染的 React 元素。 - 自定义列头渲染: 在列定义中使用
Header
prop。这是一个函数,接收{ column, table }
对象,返回要渲染的 React 元素。 - 自定义列脚渲染: 在列定义中使用
Footer
prop。这是一个函数,接收{ column, table }
对象,返回要渲染的 React 元素。 - 自定义工具栏:
renderTopToolbarCustomActions
: 在顶部工具栏左侧添加自定义元素。renderBottomToolbarCustomActions
: 在底部工具栏左侧添加自定义元素。renderToolbarInternalActions
: 完全替换或自定义顶部工具栏中间的内部动作按钮(如密度、全屏、下载等)。
- 自定义表格组件样式: 通过
muiTableProps
,muiTableHeadProps
,muiTableBodyProps
,muiTableFooterProps
,muiTableContainerProps
,muiTablePaginationProps
等 prop,可以直接传入 Material UI 组件的 prop 来定制样式或行为。例如,muiTableBodyRowProps={({ row }) => ({ sx: { backgroundColor: row.getIsSelected() ? '#e0e0e0' : undefined } })}
可以根据行是否选中来改变背景色。 - 自定义表格元素: 使用
renderRowActionMenuItems
,renderColumnActionMenuItems
等 prop 来自定义操作菜单项。
示例(在之前的示例中已经展示了 Cell
和 renderTopToolbarCustomActions
的用法):
“`jsx
// … (import MaterialReactTable and data/columns)
const columns = useMemo(
() => [
{ accessorKey: ‘name.firstName’, header: ‘名’ },
{ accessorKey: ‘name.lastName’, header: ‘姓’ },
{
accessorKey: ‘address’,
header: ‘地址’,
Cell: ({ cell }) => ( // 自定义地址单元格渲染
{/ 可以根据地址内容显示图标或其他元素 /}
),
},
{
accessorKey: ‘city’,
header: ‘城市’,
Header: ({ column }) => ( // 自定义城市列头渲染
{column.columnDef.header}
),
},
// … 其他列
],
[]
);
// … 组件渲染部分
return (
sx: {
backgroundColor: row.index % 2 === 0 ? ‘#f5f5f5’ : ‘inherit’, // 斑马纹
},
})}
// … 其他 props
/>
);
“`
11. 虚拟化 (Virtualization)
当处理成千上万行数据时,一次性渲染所有行会导致严重的性能问题。虚拟化只渲染当前可视区域及其附近的数据行,极大地提高了性能。
- 启用:
enableVirtualization={true}
。 - 注意事项:
- 需要固定行高或提供一个函数来获取行高 (
getRowHeight
)。 - 某些功能可能与虚拟化不完全兼容(例如,复杂的 sticky footer)。
- 滚动事件处理可能需要调整。
- 需要确保 Table 容器有固定的高度或最大高度,以便滚动发生。
- 需要固定行高或提供一个函数来获取行高 (
示例:
“`jsx
import React, { useMemo } from ‘react’;
import MaterialReactTable from ‘material-react-table’;
// 假设你有大量数据
const largeData = Array.from({ length: 10000 }, (_, index) => ({
id: index,
name: { firstName: FirstName${index}
, lastName: LastName${index}
},
address: Address ${index}
,
city: City${index % 10}
,
state: State${index % 5}
,
}));
const columns = useMemo(
() => [
{ accessorKey: ‘id’, header: ‘ID’, size: 80 },
{ accessorKey: ‘name.firstName’, header: ‘名’, size: 150 },
{ accessorKey: ‘name.lastName’, header: ‘姓’, size: 150 },
{ accessorKey: ‘address’, header: ‘地址’, size: 300 },
{ accessorKey: ‘city’, header: ‘城市’, size: 150 },
{ accessorKey: ‘state’, header: ‘省份’, size: 150 },
],
[]
);
const ExampleTableWithVirtualization = () => {
return (
);
};
“`
12. 服务器端数据处理 (Server-Side Data Handling)
对于大型数据集,通常需要将分页、排序、过滤等操作放在服务器端进行,以减少客户端的负担和数据传输量。Material React Table 提供了手动模式来支持这一点。
- 启用手动模式:
manualPagination={true}
manualSorting={true}
manualFiltering={true}
manualGrouping={true}
- 处理流程:
- 组件挂载时,根据初始状态(或默认状态)触发一次数据请求。
- 当用户执行分页、排序、过滤等操作时,MRT 会触发相应的
onChange
事件 (onPaginationChange
,onSortingChange
,onColumnFiltersChange
,onGlobalFilterChange
,onGroupingChange
)。 - 在事件处理函数中,更新组件的本地状态(存储当前的 pagination, sorting, filters, grouping 信息)。
- 根据更新后的状态,向服务器发送新的 API 请求。
- 服务器根据请求参数处理数据(分页、排序、过滤、分组),返回当前页的数据和总行数。
- 更新组件的
data
prop 为服务器返回的当前页数据。 - 更新
rowCount
prop 为服务器返回的总行数(仅用于分页)。
- 显示加载状态: 使用
state.isLoading
或state.showProgressBars
控制加载指示器。
示例(伪代码):
“`jsx
import React, { useMemo, useState, useEffect, useCallback } from ‘react’;
import MaterialReactTable from ‘material-react-table’;
// … columns definition
const ExampleTableServerSide = () => {
const columns = useMemo(/ … /, []);
// 表格状态,用于发送到服务器
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: 10,
});
const [sorting, setSorting] = useState([]);
const [columnFilters, setColumnFilters] = useState([]);
const [globalFilter, setGlobalFilter] = useState(”);
const [grouping, setGrouping] = useState([]);
// 从服务器获取的数据和总行数
const [data, setData] = useState([]);
const [rowCount, setRowCount] = useState(0);
const [isLoading, setIsLoading] = useState(false); // 加载状态
const fetchData = useCallback(async () => {
setIsLoading(true);
// 构建请求参数,例如:
// {
// page: pagination.pageIndex + 1, // 服务器通常从 1 开始
// pageSize: pagination.pageSize,
// orderBy: sorting.map(s => ${s.id}:${s.desc ? 'desc' : 'asc'}
).join(‘,’),
// filters: columnFilters.map(f => ${f.id}:${f.value}
).join(‘;’),
// globalFilter: globalFilter,
// groupBy: grouping.join(‘,’),
// }
try {
// const response = await fetch('/api/data', {
// method: 'POST', // 或者 GET,根据后端接口定
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ pagination, sorting, columnFilters, globalFilter, grouping }),
// });
// const result = await response.json();
// 模拟 API 调用延迟和返回数据
await new Promise(resolve => setTimeout(resolve, 500));
const result = {
data: data.slice(pagination.pageIndex * pagination.pageSize, (pagination.pageIndex + 1) * pagination.pageSize), // 模拟分页
totalRowCount: data.length, // 模拟总行数
};
// 在实际应用中,服务器应该根据 sorting, filtering, grouping 处理数据并返回
console.log("模拟服务器请求参数:", { pagination, sorting, columnFilters, globalFilter, grouping });
setData(result.data);
setRowCount(result.totalRowCount);
} catch (error) {
console.error("Error fetching data:", error);
// 处理错误
} finally {
setIsLoading(false);
}
}, [pagination, sorting, columnFilters, globalFilter, grouping]); // 当这些状态变化时重新获取数据
// 在状态变化时触发数据获取
useEffect(() => {
fetchData();
}, [fetchData]); // fetchData 函数会在其依赖变化时更新
return (
<MaterialReactTable
columns={columns}
data={data} // 传递当前页数据
rowCount={rowCount} // 传递总行数
enablePagination // 启用分页 UI
enableSorting // 启用排序 UI
enableFiltering // 启用过滤 UI
enableGrouping // 启用分组 UI (如果服务器端支持)
manualPagination // 开启手动分页模式
manualSorting // 开启手动排序模式
manualFiltering // 开启手动过滤模式
manualGrouping // 开启手动分组模式 (如果服务器端支持)
onPaginationChange={setPagination} // 监听分页变化
onSortingChange={setSorting} // 监听排序变化
onColumnFiltersChange={setColumnFilters} // 监听列过滤变化
onGlobalFilterChange={setGlobalFilter} // 监听全局过滤变化
onGroupingChange={setGrouping} // 监听分组变化
state={{ // 将外部状态传递给表格,以控制 UI 显示
pagination,
sorting,
columnFilters,
globalFilter,
grouping,
isLoading, // 显示加载状态
// showProgressBars: isLoading, // 也可以使用进度条
}}
// 其他你想要启用的功能
enableDensityToggle
enableFullScreenToggle
enableColumnActions
enableColumnFilters
enableGlobalFilter
/>
);
};
“`
服务器端数据处理是构建处理大量数据的强大表格的关键,它将数据处理的计算量转移到服务器,使客户端保持轻量和响应迅速。
高级定制和最佳实践
-
国际化 (i18n): MRT 支持国际化。你可以通过
localization
prop 传入一个本地化对象。MRT 官方提供了一些语言包,或者你可以创建自己的。
“`jsx
import { MaterialReactTable, MRT_Localization_ZH_HANS } from ‘material-react-table’;// … columns, data
``
enableVirtualization
* **主题定制:** 由于 MRT 基于 Material UI,你可以通过 Material UI 的主题系统来自定义表格的颜色、字体、间距等。只需在你的应用根部提供一个 Material UI 主题即可。
* **性能优化:**
* 对于大数据集,优先考虑启用。
manual…
* 对于海量数据,采用服务器端数据处理 (props)。
columns
* 确保和
dataprop 在组件重渲染时不会发生不必要的改变(使用
useMemo或
useCallback)。
Cell` 函数内部执行昂贵的计算。
* 自定义单元格渲染时,避免在
* 可访问性 (Accessibility): MRT 在构建时考虑了 WAI-ARIA 标准,提供了良好的键盘导航和屏幕阅读器支持。在自定义内容时,也要注意保持可访问性。
* 错误处理和空状态: 考虑数据加载失败或数据为空时的 UI 表现,可以在父组件中根据加载状态和数据是否为空来渲染不同的内容或在表格外部显示提示。
总结
Material React Table 是一个功能全面、灵活且易于与 Material UI 集成的 React 表格库。它基于高性能的 TanStack Table,提供了从基础的分页、排序、过滤到高级的列固定、分组、虚拟化、服务器端处理等企业级表格所需的一切功能。通过丰富的 API 和灵活的定制选项,你可以构建出满足各种复杂需求的强大数据表格,极大地提升开发效率和用户体验。
无论你是需要一个简单的表格来展示数据,还是需要一个复杂的、支持大数据集和各种交互功能的数据管理界面,Material React Table 都能成为你的有力工具。投入时间学习和掌握它的各项特性,将为你的 React 应用开发带来显著的价值。
现在,是时候将这些知识应用到你的项目中,开始构建你的强大 React 表格了!