Material React Table 介绍与使用 – wiki基地


Material React Table 介绍与深度使用指南

引言

在现代 Web 应用开发中,表格是展示大量结构化数据最常见且重要的方式之一。一个功能丰富、交互友好且性能高效的表格组件,对于提升用户体验至关重要。对于使用 React 和 Material UI 构建的应用来说,选择一个与之完美契合的表格库显得尤为重要。

Material React Table (MRT) 正是为了满足这一需求而诞生的。它基于强大的 TanStack Table v8 库(以前称为 React Table v8)和流行的 Material UI 组件库。MRT 继承了 TanStack Table 灵活且无头(headless)的特性,同时利用 Material UI 提供了开箱即用的漂亮外观和丰富的组件,大大简化了复杂表格的构建过程。

本文将详细介绍 Material React Table,从它的核心概念、安装、基本使用,逐步深入到各种高级功能、定制化方法以及性能优化技巧,帮助您充分发挥 MRT 的威力,构建出既美观又强大的数据表格。

为什么选择 Material React Table?

市面上有众多 React 表格库,例如 react-table (TanStack Table)、ag-grid、react-data-grid 等。那么,Material React Table 有何独特优势,让您考虑选择它呢?

  1. 深度整合 Material UI: 如果您的项目已经使用了 Material UI,MRT 将是您的首选。它直接使用 Material UI 的组件和主题系统,保持了应用整体风格的一致性,无需额外的样式配置,开发效率高。
  2. 基于 TanStack Table v8: MRT 利用了 TanStack Table v8 的核心逻辑,这是一个非常成熟、高性能且功能丰富的表格库。这意味着 MRT 自然地继承了 TanStack Table 的强大能力,如灵活的列定义、强大的状态管理、以及对客户端/服务器端数据处理的良好支持。
  3. 开箱即用的丰富功能: MRT 在 TanStack Table 的基础上,提供了大量预设的功能,如排序、过滤(全局和列级别)、分页、行选择、列隐藏/排序/调整大小、行展开、数据密度切换、导出等等。许多复杂的功能只需简单的配置即可启用,极大地减少了手动实现的工作量。
  4. 高度可定制性: 尽管提供了丰富的功能,MRT 依然保持了高度的可定制性。您可以轻松地自定义表格的各个部分,包括单元格渲染、头部渲染、工具栏、甚至整个表格的样式。这使得您可以根据具体需求调整表格的外观和行为。
  5. 良好的维护和社区支持: 作为基于两大流行库(TanStack Table 和 Material UI)的组合,MRT 受益于它们庞大的社区和活跃的维护。MRT 本身也在积极更新中。

总而言之,如果您正在使用 Material UI 并且需要一个功能丰富、易于使用且高度可定制的 React 表格解决方案,Material React Table 是一个非常理想的选择。

前置条件

在开始使用 Material React Table 之前,请确保您的开发环境满足以下条件:

  1. Node.js 和 npm/yarn: 您需要安装 Node.js,它包含了 npm 包管理器。您也可以选择使用 yarn 或 pnpm。
  2. React 项目: 您需要一个现有的 React 项目。这可以是使用 Create React App、Vite、Next.js 或其他工具创建的项目。
  3. Material UI 安装: MRT 依赖于 Material UI v5 或更高版本。如果您的项目尚未安装 Material UI,请先进行安装。安装命令通常如下:

    “`bash
    npm install @mui/material @emotion/react @emotion/styled

    或者

    yarn add @mui/material @emotion/react @emotion/styled
    同时,您可能还需要安装 Material UI 的 Icons 库,因为 MRT 默认使用一些图标:bash
    npm install @mui/icons-material

    或者

    yarn add @mui/icons-material
    “`

安装 Material React Table

一旦您的项目具备了上述前置条件,就可以安装 Material React Table 了。

使用 npm 或 yarn 执行以下命令:

“`bash
npm install material-react-table

或者

yarn add material-react-table
“`

安装完成后,您就可以在项目中使用 material-react-table 组件了。

基本使用

使用 Material React Table 构建一个最基本的表格非常简单。您只需要准备两样东西:表格的列定义和要显示的数据。

1. 准备数据

数据应该是一个对象数组。数组中的每个对象代表表格的一行,对象的属性名对应着列的标识符。

javascript
// 定义一些示例数据
const data = [
{
name: '张三',
age: 28,
city: '北京',
},
{
name: '李四',
age: 35,
city: '上海',
},
{
name: '王五',
age: 22,
city: '广州',
},
{
name: '赵六',
age: 40,
city: '深圳',
},
];

2. 定义列

列定义是一个对象数组,每个对象描述了表格的一列。至少需要指定列的 header (列头显示的文本) 和 accessorKeyaccessorFn (如何从数据对象中获取该列的值)。

  • accessorKey: 如果数据对象的属性名与列需要的值直接对应,使用这个属性。
  • accessorFn: 如果需要对数据进行处理或计算才能得到列的值,可以使用一个函数。

javascript
// 定义表格列
const columns = [
{
accessorKey: 'name', // 数据对象中的属性名
header: '姓名', // 列头显示的文本
},
{
accessorKey: 'age',
header: '年龄',
},
{
accessorKey: 'city',
header: '城市',
},
];

3. 渲染表格

现在,将数据和列定义传递给 MaterialReactTable 组件进行渲染。

“`javascript
import React, { useMemo } from ‘react’;
import { MaterialReactTable } from ‘material-react-table’;

const SimpleTable = () => {
// 数据和列定义通常在组件外部定义,或者通过 hooks 获取

const data = useMemo(
() => [
{ name: ‘张三’, age: 28, city: ‘北京’ },
{ name: ‘李四’, age: 35, city: ‘上海’ },
{ name: ‘王五’, age: 22, city: ‘广州’ },
{ name: ‘赵六’, age: 40, city: ‘深圳’ },
],
[] // useMemo 确保数据和列定义不会在每次渲染时重新创建,优化性能
);

const columns = useMemo(
() => [
{ accessorKey: ‘name’, header: ‘姓名’ },
{ accessorKey: ‘age’, header: ‘年龄’ },
{ accessorKey: ‘city’, header: ‘城市’ },
],
[]
);

return (

);
};

export default SimpleTable;
“`

在父组件中渲染 SimpleTable,您就能看到一个基础的 Material React Table。

核心功能与配置

Material React Table 提供了大量开箱即用的功能,可以通过简单的 props 进行配置。以下是一些常用功能的介绍和使用方法。

1. 排序 (Sorting)

默认情况下,MRT 的表格是不可排序的。通过设置 enableSorting prop 可以启用排序功能。

javascript
<MaterialReactTable
columns={columns}
data={data}
enableSorting // 启用所有列的排序
/>

您也可以在单个列定义中通过 enableSorting: false 来禁用特定列的排序。

javascript
const columns = useMemo(
() => [
{ accessorKey: 'name', header: '姓名', enableSorting: false }, // 禁用此列排序
{ accessorKey: 'age', header: '年龄' },
{ accessorKey: 'city', header: '城市' },
],
[]
);

多列排序

默认情况下,MRT 支持按住 Shift 键进行多列排序。如果您想允许用户不按 Shift 键直接点击进行多列排序,可以使用 enableMultiSort

javascript
<MaterialReactTable
columns={columns}
data={data}
enableSorting
enableMultiSort // 允许不按 Shift 键进行多列排序
/>

2. 过滤 (Filtering)

过滤是查找特定数据行的重要功能。MRT 支持全局过滤和列级别过滤。

全局过滤 (Global Filtering)

全局过滤器通常是一个位于表格顶部的搜索框,用户输入的文本会在所有可搜索的列中进行匹配。

javascript
<MaterialReactTable
columns={columns}
data={data}
enableGlobalFilter // 启用全局过滤器
/>

默认情况下,accessorKey 为字符串或数字类型的列是可全局搜索的。您可以在列定义中使用 enableGlobalFilter: false 来排除特定列。

列级别过滤 (Per-Column Filtering)

每个列头下方都可以显示一个独立的过滤器,用于仅过滤该列的数据。

javascript
<MaterialReactTable
columns={columns}
data={data}
enableColumnFilters // 启用列级别过滤器
/>

过滤变体 (Filter Variants)

MRT 支持多种过滤匹配方式(变体),如文本包含、等于、大于、日期范围等。您可以在列定义中通过 filterVariant 指定。

例如,为年龄列设置数字范围过滤:

“`javascript
const columns = useMemo(
() => [
{ accessorKey: ‘name’, header: ‘姓名’ },
{ accessorKey: ‘age’, header: ‘年龄’, filterVariant: ‘range’ }, // 使用范围过滤
{ accessorKey: ‘city’, header: ‘城市’ },
],
[]
);


“`

常用的 filterVariant 值包括:text, select, multi-select, range, date, date-range 等。对于 selectmulti-select,您可能需要提供过滤选项。

3. 分页 (Pagination)

MRT 默认提供客户端分页功能。

javascript
<MaterialReactTable
columns={columns}
data={data}
enablePagination // 启用分页,默认每页10条
/>

您可以通过 initialStatestate prop 控制分页状态,如初始页码和每页条数。

javascript
<MaterialReactTable
columns={columns}
data={data}
enablePagination
initialState={{ pagination: { pageSize: 5, pageIndex: 0 } }} // 初始每页显示5条,从第一页开始
/>

服务器端分页

对于大型数据集,客户端分页可能会导致性能问题,因为需要一次性加载所有数据。此时,您应该使用服务器端分页。这需要您手动管理分页状态,并根据当前页码和每页条数从服务器只请求需要的数据。

要启用服务器端控制,您需要设置 manualPaginationtrue,并通过 rowCount 告知表格总共有多少条数据(用于计算总页数)。您还需要监听分页状态变化,并触发数据请求。

“`javascript
import React, { useState, useEffect, useMemo } from ‘react’;
import { MaterialReactTable } from ‘material-react-table’;

const ServerSidePaginationTable = () => {
const [loading, setLoading] = useState(true);
const [data, setData] = useState([]);
const [rowCount, setRowCount] = useState(0);
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: 10,
});

// 模拟从服务器获取数据的函数
const fetchData = async (pageIndex, pageSize) => {
setLoading(true);
// 实际应用中,这里会发送API请求,例如:
// const response = await fetch(/api/data?page=${pageIndex}&size=${pageSize});
// const result = await response.json();
// setData(result.data);
// setRowCount(result.totalCount);

// 模拟延迟和返回数据
await new Promise(resolve => setTimeout(resolve, 500));
const totalItems = 100; // 假设服务器总共有100条数据
const startIndex = pageIndex * pageSize;
const endIndex = Math.min(startIndex + pageSize, totalItems);
const simulatedData = Array.from({ length: endIndex - startIndex }).map((_, index) => ({
    name: `用户${startIndex + index + 1}`,
    age: 20 + (startIndex + index + 1) % 30,
    city: ['北京', '上海', '广州', '深圳'][Math.floor((startIndex + index) / 10) % 4],
}));
setData(simulatedData);
setRowCount(totalItems);
setLoading(false);

};

// 监听分页状态变化,触发数据获取
useEffect(() => {
fetchData(pagination.pageIndex, pagination.pageSize);
}, [pagination.pageIndex, pagination.pageSize]); // 依赖项

const columns = useMemo(
() => [
{ accessorKey: ‘name’, header: ‘姓名’ },
{ accessorKey: ‘age’, header: ‘年龄’ },
{ accessorKey: ‘city’, header: ‘城市’ },
],
[]
);

return (

);
};

export default ServerSidePaginationTable;
“`

服务器端排序和过滤的原理类似,需要设置 manualSortingmanualFiltering,监听相应的状态变化,并将状态(如排序字段、排序方向、过滤值)发送给服务器,由服务器返回处理后的数据。

4. 行选择 (Row Selection)

启用行选择功能可以在表格行的最左侧显示复选框,允许用户选择一行或多行。

javascript
<MaterialReactTable
columns={columns}
data={data}
enableRowSelection // 启用行选择
/>

要获取当前选中的行,可以通过 onRowSelectionChange 监听状态变化,或者通过 state.rowSelection 获取当前选中的行 IDs。

“`javascript
import React, { useState, useMemo } from ‘react’;
import { MaterialReactTable } from ‘material-react-table’;

const SelectableTable = ({ data, columns }) => {
const [rowSelection, setRowSelection] = useState({}); // 存储选中的行 IDs

const handleRowSelectionChange = (updater) => {
// updater 可以是新的状态对象,也可以是接受旧状态并返回新状态的函数
setRowSelection(updater);
console.log(‘选中的行:’, Object.keys(updater)); // updater 的键是行的ID
};

return (

);
};
“`

默认情况下,行 ID 是数据对象本身。如果数据对象没有唯一标识,或者您想使用特定的属性作为 ID,可以在列定义中指定 getRowId 函数。

javascript
<MaterialReactTable
columns={columns}
data={data}
enableRowSelection
getRowId={(row) => row.userId} // 假设数据对象有 userId 属性作为唯一ID
/>

5. 行操作 (Row Actions)

通常需要在表格的每一行添加一些操作按钮,如“编辑”、“删除”或“查看详情”。MRT 通过 enableRowActionsrenderRowActions prop 支持此功能。

“`javascript
import React, { useMemo } from ‘react’;
import { MaterialReactTable } from ‘material-react-table’;
import { Box, IconButton, Tooltip } from ‘@mui/material’;
import EditIcon from ‘@mui/icons-material/Edit’;
import DeleteIcon from ‘@mui/icons-material/Delete’;

const ActionTable = ({ data, columns }) => {
const handleEditRow = (row) => {
console.log(‘编辑行:’, row.original); // row.original 包含原始数据
// 执行编辑逻辑,例如打开一个模态框
};

const handleDeleteRow = (row) => {
console.log(‘删除行:’, row.original);
// 执行删除逻辑,例如发送API请求
if (window.confirm(确定要删除用户 ${row.original.name} 吗?)) {
// 执行删除操作
}
};

return (
( // renderRowActions prop 接收 row 和 table 对象


handleEditRow(row)}>




handleDeleteRow(row)}>




)}
/>
);
};
“`

renderRowActions 是一个渲染函数,它会为每一行渲染一次。您可以根据需要返回任何 React 元素,通常是包含操作按钮的 Material UI Box。row 对象包含了当前行的数据和状态信息,通过 row.original 可以访问原始数据。

6. 行展开 (Row Expanding)

有时,您可能想在表格行下方显示更多详细信息。MRT 支持行展开功能。

“`javascript
import React, { useMemo } from ‘react’;
import { MaterialReactTable } from ‘material-react-table’;
import { Box, Typography } from ‘@mui/material’;

const ExpandableTable = ({ data, columns }) => {
return (
( // renderDetailPanel 渲染展开的内容

详情信息 – {row.original.name}
年龄: {row.original.age}
城市: {row.original.city}
{/ 添加更多详细信息 /}

)}
/>
);
};
“`

renderDetailPanel prop 是一个渲染函数,它接收 row 对象作为参数,您可以利用 row.original 访问当前行的数据,并返回任何 React 元素来显示详细信息。

7. 列的隐藏、排序和调整大小

MRT 提供了控制列可见性、顺序和宽度的功能。

  • 列隐藏 (Column Hiding): 启用 enableHiding 会在表格顶部工具栏添加一个“显示/隐藏列”的切换按钮。
    javascript
    <MaterialReactTable
    columns={columns}
    data={data}
    enableHiding // 启用列隐藏菜单
    />

    您也可以在列定义中通过 enableHiding: false 来阻止特定列被隐藏。
  • 列排序 (Column Ordering): 启用 enableColumnOrdering 允许用户通过拖拽列头来改变列的显示顺序。
    javascript
    <MaterialReactTable
    columns={columns}
    data={data}
    enableColumnOrdering // 启用列拖拽排序
    />
  • 列调整大小 (Column Resizing): 启用 enableColumnResizing 允许用户拖拽列头之间的分隔线来调整列的宽度。
    javascript
    <MaterialReactTable
    columns={columns}
    data={data}
    enableColumnResizing // 启用列宽调整
    />

您可以组合使用这些功能,通过 initialStatestate 来控制初始状态或受控地管理它们。

8. 数据密度和样式 (Density and Styling)

MRT 提供了调整表格行密度的功能,并提供了多种方式进行样式定制。

  • 数据密度 (Data Density): 启用 enableDensityToggle 会在表格顶部工具栏添加一个切换按钮,允许用户在标准、中等、紧凑三种密度之间切换。
    javascript
    <MaterialReactTable
    columns={columns}
    data={data}
    enableDensityToggle // 启用数据密度切换
    />
  • 样式定制 (Styling): MRT 允许您通过多种方式覆盖或添加样式:
    • 直接通过 props: MRT 提供了许多以 mui 开头的 props,如 muiTableContainerProps, muiTableHeadProps, muiTableBodyProps, muiTableRowProps, muiTableHeadCellProps, muiTableBodyCellProps 等。您可以将 Material UI 的 sx prop 或 style prop 传递给它们来应用样式。
      javascript
      <MaterialReactTable
      columns={columns}
      data={data}
      muiTableContainerProps={{ sx: { maxHeight: '500px' } }} // 容器样式
      muiTableHeadCellProps={{
      sx: { fontWeight: 'bold', backgroundColor: '#f5f5f5' }, // 表头单元格样式
      }}
      muiTableBodyCellProps={{ sx: { borderRight: '1px solid #eee' } }} // 表格体单元格样式
      />
    • 在列定义中定制单元格/头部样式: 在列定义中使用 muiTableHeadCellPropsmuiTableBodyCellProps 为特定列设置样式。
      javascript
      const columns = useMemo(
      () => [
      { accessorKey: 'name', header: '姓名' },
      {
      accessorKey: 'age',
      header: '年龄',
      muiTableBodyCellProps: ({ cell }) => ({
      sx: {
      color: cell.getValue() > 30 ? 'red' : 'green', // 根据年龄条件渲染颜色
      },
      }),
      },
      { accessorKey: 'city', header: '城市' },
      ],
      []
      );

      注意 muiTableBodyCellProps 可以是一个接收 cell 对象的函数,这样可以根据单元格的值进行条件样式设置。
    • 使用 Material UI 的 Theme: MRT 会遵循您的 Material UI 主题设置。您可以定制主题的调色板、字体、组件默认样式等来影响表格的外观。
    • CSS 类或 Styles API: 您也可以通过标准的 CSS 类或 Material UI 的 Styles API 来定位和样式化 MRT 的组件。

9. 加载状态 (Loading States)

处理数据加载是异步操作中常见的场景。MRT 提供了内置的加载指示器支持。

通过设置 state.isLoadingtrue,可以在表格顶部显示一个线性加载条。

“`javascript
const [loading, setLoading] = useState(true);
// … fetchData 函数设置 loading 状态


“`

您还可以通过 enableLoadingScreen prop 启用一个覆盖整个表格的加载骨架屏。

javascript
<MaterialReactTable
columns={columns}
data={data}
enableLoadingScreen // 启用加载骨架屏
state={{ isLoading: loading }} // 控制加载状态
/>

10. 本地化 (Localization)

MRT 支持多种语言的本地化。您可以通过 localization prop 传递一个包含翻译文本的对象。MRT 预设了多种语言的翻译文件,您可以直接导入使用。

“`javascript
import { MaterialReactTable, MRT_Localization_ZH_CN } from ‘material-react-table’;


“`

如果需要自定义翻译文本或添加 MRT 尚未提供的语言,可以创建一个符合格式的本地化对象并传递给 localization prop。

高级定制化

除了通过 props 启用和配置内置功能外,MRT 还提供了强大的渲染 prop,允许您完全控制表格的特定部分的显示。

1. 自定义单元格渲染 (Custom Cell Rendering)

通过在列定义中使用 Cell prop,您可以完全控制如何渲染该列的每个单元格。这对于格式化数据、显示图片、链接、按钮等非常有用。

Cell prop 是一个渲染函数,它接收一个包含 cell 对象的参数。cell 对象包含了单元格的各种信息,最常用的是 cell.getValue() 来获取单元格的原始值,以及 row.original 来获取整行数据。

javascript
const columns = useMemo(
() => [
{
accessorKey: 'name',
header: '姓名',
Cell: ({ cell, row }) => ( // 自定义姓名单元格渲染
<Box sx={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
{/* 假设数据有 avatar 属性 */}
{/* <img
alt="avatar"
height={30}
src={row.original.avatar}
loading="lazy"
style={{ borderRadius: '50%' }}
/> */}
<span>{cell.getValue()}</span> {/* 显示姓名 */}
</Box>
),
},
{
accessorKey: 'age',
header: '年龄',
Cell: ({ cell }) => (
// 根据年龄值显示不同颜色文本
<Typography color={cell.getValue() > 30 ? 'error' : 'success'}>
{cell.getValue()} 岁
</Typography>
),
},
{ accessorKey: 'city', header: '城市' },
{
accessorKey: 'action', // 一个不存在于原始数据,仅用于渲染的列
header: '操作',
enableSorting: false, // 操作列通常不需要排序和过滤
enableColumnFilter: false,
Cell: ({ row }) => (
<Button variant="outlined" size="small" onClick={() => alert(`查看用户 ${row.original.name}`)}>
查看
</Button>
),
},
],
[]
);

在上述例子中,我们展示了如何:
* 在姓名列中结合文本和图片(注释掉的部分)。
* 根据年龄的值改变文本颜色并添加单位。
* 创建一个“操作”列,它不对应数据中的某个字段,而是用于渲染一个按钮,并可以访问当前行的数据。

2. 自定义头部渲染 (Custom Header Rendering)

类似地,您可以使用列定义中的 Header prop 来自定义列头的显示。这可以用于添加图标、提示信息或复杂的标题布局。

Header prop 也是一个渲染函数,它接收一个包含 column 对象的参数,可以通过 column.columnDef.header 获取原始的列头文本。

javascript
const columns = useMemo(
() => [
{
accessorKey: 'name',
header: '姓名',
Header: ({ column }) => ( // 自定义姓名列头
<Box sx={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<PersonIcon /> {/* 添加一个 Material UI Icon */}
{column.columnDef.header} {/* 显示原始列头文本 */}
</Box>
),
},
{ accessorKey: 'age', header: '年龄' },
{ accessorKey: 'city', header: '城市' },
],
[]
);

3. 自定义工具栏 (Custom Toolbars)

MRT 提供了顶部工具栏和底部工具栏,用于放置全局搜索框、列显示/隐藏按钮、分页控制器等。您可以通过 renderTopToolbarCustomActionsrenderBottomToolbarCustomActions prop 在这些工具栏中添加自定义内容。

这些 props 也是渲染函数,它们接收一个包含 table 对象的参数,通过 table 对象可以访问表格的各种状态和方法。

“`javascript
import React, { useMemo } from ‘react’;
import { MaterialReactTable } from ‘material-react-table’;
import { Box, Button } from ‘@mui/material’;
import AddIcon from ‘@mui/icons-material/Add’;

const CustomToolbarTable = ({ data, columns }) => {

const handleAddNew = () => {
alert(‘点击了“添加新用户”按钮’);
// 执行添加新用户的逻辑,例如打开一个表单模态框
};

return (
( // 在顶部工具栏左侧添加自定义内容

)}
// renderBottomToolbarCustomActions={({ table }) => ( … )} // 也可以在底部工具栏添加
/>
);
};
“`

通过 renderTopToolbarCustomActions,我们在顶部工具栏的默认内容旁边添加了一个“添加新用户”按钮。您可以根据需要添加任何组件,例如数据导出按钮、批量操作按钮等。

4. 整体表格定制

除了上述特定部分的定制,您还可以通过 muiTablePropsmuiTableHeadPropsmuiTableBodyProps 等 props 来调整表格整体结构和样式,或者通过 layoutMode prop 设置表格布局模式 (如 'static', 'grid')。

5. 与模态框/表单集成

在实际应用中,行操作(编辑、查看、创建)通常会涉及到打开模态框或抽屉,并在其中显示表单。

您可以在 renderRowActionsrenderTopToolbarCustomActions 的处理函数中(如 handleEditRow, handleAddNew)设置状态来控制模态框的显示,并将当前行数据或默认数据传递给模态框中的表单组件。

这是一个基本示例的伪代码结构:

“`javascript
import React, { useState, useMemo } from ‘react’;
import { MaterialReactTable } from ‘material-react-table’;
import { Box, IconButton, Tooltip, Button, Dialog, DialogTitle, DialogContent, DialogActions, TextField } from ‘@mui/material’;
import EditIcon from ‘@mui/icons-material/Edit’;
import AddIcon from ‘@mui/icons-material/Add’;

// 假设有一个简单的用户表单组件
const UserForm = ({ initialData, onSave, onCancel }) => {
const [formData, setFormData] = useState(initialData || { name: ”, age: ”, city: ” });

const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
};

const handleSave = () => {
    onSave(formData);
};

return (
    <Box component="form" sx={{ '& .MuiTextField-root': { m: 1, width: '25ch' } }} noValidate autoComplete="off">
        <TextField label="姓名" name="name" value={formData.name} onChange={handleChange} />
        <TextField label="年龄" name="age" value={formData.age} onChange={handleChange} type="number" />
        <TextField label="城市" name="city" value={formData.city} onChange={handleChange} />
        <DialogActions>
            <Button onClick={onCancel}>取消</Button>
            <Button onClick={handleSave}>保存</Button>
        </DialogActions>
    </Box>
);

};

const IntegratedTable = ({ initialData: data }) => { // 假设 initialData 是从外部传入的
const [tableData, setTableData] = useState(data); // 表格使用本地状态的数据
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingRow, setEditingRow] = useState(null); // null 表示创建,否则是编辑的行数据

const handleAddNew = () => {
    setEditingRow(null); // 新增操作
    setIsModalOpen(true);
};

const handleEditRow = (row) => {
    setEditingRow(row.original); // 编辑操作,传入当前行原始数据
    setIsModalOpen(true);
};

const handleDeleteRow = (row) => {
    if (window.confirm(`确定要删除用户 ${row.original.name} 吗?`)) {
        setTableData(prevData => prevData.filter(item => item !== row.original)); // 简单的本地删除
    }
};

const handleSaveUser = (userData) => {
    if (editingRow) {
        // 编辑逻辑:更新数据
        setTableData(prevData =>
            prevData.map(item => (item === editingRow ? userData : item))
        );
    } else {
        // 创建逻辑:添加新数据
        setTableData(prevData => [...prevData, userData]); // 注意:这里需要一个唯一的ID,真实应用中通常由后端生成
    }
    setIsModalOpen(false);
};

const columns = useMemo(
    () => [
        { accessorKey: 'name', header: '姓名' },
        { accessorKey: 'age', header: '年龄' },
        { accessorKey: 'city', header: '城市' },
    ],
    []
);

return (
    <>
        <MaterialReactTable
            columns={columns}
            data={tableData} // 使用本地状态数据
            enableRowActions
            renderRowActions={({ row }) => (
                <Box sx={{ display: 'flex', gap: '0.5rem' }}>
                    <Tooltip title="编辑">
                        <IconButton onClick={() => handleEditRow(row)}>
                            <EditIcon />
                        </IconButton>
                    </Tooltip>
                    <Tooltip title="删除">
                        <IconButton color="error" onClick={() => handleDeleteRow(row)}>
                            <DeleteIcon />
                        </IconButton>
                    </Tooltip>
                </Box>
            )}
            renderTopToolbarCustomActions={() => (
                <Button variant="contained" startIcon={<AddIcon />} onClick={handleAddNew}>
                    添加新用户
                </Button>
            )}
        />

        {/* 编辑/创建用户的模态框 */}
        <Dialog open={isModalOpen} onClose={() => setIsModalOpen(false)}>
            <DialogTitle>{editingRow ? '编辑用户' : '添加新用户'}</DialogTitle>
            <DialogContent>
                 {/* 将当前数据传递给表单 */}
                <UserForm
                    initialData={editingRow}
                    onSave={handleSaveUser}
                    onCancel={() => setIsModalOpen(false)}
                />
            </DialogContent>
        </Dialog>
    
);

};

// 示例使用
const initialUsers = [
{ name: ‘张三’, age: 28, city: ‘北京’ },
{ name: ‘李四’, age: 35, city: ‘上海’ },
];

const App = () => ;
“`

这个例子展示了如何使用组件状态 (isModalOpen, editingRow) 来控制外部模态框的显示和传递数据。在实际应用中,数据操作(增删改)通常会涉及到与后端 API 的交互,handleDeleteRowhandleSaveUser 函数中会包含 API 调用,并在成功后更新表格数据。

性能考虑

对于需要显示大量数据(成千上万甚至更多)的表格,性能是一个关键问题。MRT 在 TanStack Table 的基础上提供了一些性能优化选项:

  1. 服务器端处理: 前面提到的服务器端分页、排序、过滤是处理大数据集最有效的方法。将数据处理的负担转移到后端可以显著减少前端需要处理的数据量和计算量。
  2. 虚拟化 (Virtualization): 对于行数非常多的情况,即使是服务器端分页,一次加载一页数据也可能导致表格渲染元素过多而变慢(尽管比加载所有数据好得多)。虚拟化只渲染当前视口可见的行,极大地提高了渲染性能。MRT 通过 enableVirtualization prop 提供虚拟化支持。
    javascript
    <MaterialReactTable
    columns={columns}
    data={data}
    enableVirtualization // 启用行虚拟化
    // 需要固定高度的容器
    muiTableContainerProps={{ sx: { maxHeight: '600px' } }}
    />

    启用虚拟化通常需要表格容器有一个固定的或最大的高度,以便虚拟化算法知道何时渲染哪些行。
  3. 使用 useMemo: 在定义 columnsdata 时使用 useMemo 是一个良好的实践,可以防止在组件重新渲染时重复创建这些对象,从而避免不必要的表格重新计算和渲染。
  4. 避免在渲染函数中执行复杂计算:CellHeaderrenderDetailPanel 等渲染函数中避免执行耗时操作。如果需要复杂的数据处理,尽量提前在数据加载或处理阶段完成。

何时选择 Material React Table?

  • 您的项目使用 React 和 Material UI。
  • 您需要一个功能丰富的表格,例如排序、过滤、分页、选择、行操作等。
  • 您希望表格具有 Material Design 的外观,并与您的应用主题保持一致。
  • 您希望能够相对容易地定制表格的各个部分。
  • 您需要处理中等或大量数据,并可能需要服务器端处理或虚拟化。

如果您不使用 Material UI,或者需要一个极度轻量级、对样式没有任何预设的表格库(例如,您想完全自由地使用自己的样式系统),或者需要一些 MRT 尚未支持的非常特殊的表格功能,那么您可能需要考虑其他选项。

总结

Material React Table 是一个强大、灵活且易于使用的 React 表格组件,特别适合与 Material UI 项目集成。它结合了 TanStack Table v8 的核心能力和 Material UI 的美观与组件生态,提供了丰富的功能和高度的可定制性。

从基本的表格展示,到复杂的排序、过滤、分页、行操作和定制化渲染,MRT 提供了全面的解决方案。通过理解其核心概念(列定义、数据)和常用 props,您可以快速构建出功能强大的表格。对于大型数据集,掌握服务器端处理和虚拟化等性能优化技巧至关重要。

希望这篇详细指南能帮助您深入了解和有效地使用 Material React Table,在您的 React 项目中创建出色的数据表格体验。

请记住,最佳的学习方式是实践。建议您在自己的项目中安装 Material React Table,并尝试实现本文介绍的各种功能。查阅官方文档也是深入学习的宝贵资源。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部