如何在 React 中使用 React Table 创建表格 – wiki基地


精通 React Table (v8): 构建强大、灵活的 React 表格

在现代 Web 开发中,数据表格是展示和操作集合信息的基石。无论是简单的信息罗列,还是需要排序、筛选、分页等复杂交互的大型数据集,一个功能强大且灵活的表格组件都至关重要。React 生态系统中有许多表格库,但 React Table(现在由 TanStack 维护,称为 TanStack Table,我们通常仍称其核心为 React Table)以其独特的 “headless” 设计理念脱颖而出,提供了无与伦比的控制力和灵活性。

本文将深入探讨如何在 React 项目中使用最新版本的 React Table (v8) 来创建功能丰富、可定制的表格。我们将从基础概念入手,逐步构建一个包含排序、筛选、分页和行选择等常见功能的表格,并讨论其核心优势和最佳实践。

什么是 React Table (v8 / TanStack Table)?

React Table v8 与其前身 v7 有一个显著的区别:它是一个 Headless UI 库。这意味着 React Table 本身不提供任何 UI 组件或样式。它专注于处理表格的复杂逻辑——状态管理、数据处理(排序、过滤、分页、分组、聚合等)、API 设计——并将最终的渲染控制权完全交给开发者。

为什么选择 Headless UI?

  1. 完全的渲染控制: 你可以自由选择使用标准的 HTML <table> 元素、<div> 结构、甚至是 Canvas 或 SVG 来渲染你的表格。库不限制你的标记语言。
  2. 自由的样式: 因为库不输出 UI,你可以无缝集成任何你喜欢的样式方案,无论是普通的 CSS、CSS Modules、Sass、Styled Components、Tailwind CSS 还是 Material UI 等组件库。
  3. 更小的包体积: 库只包含核心逻辑,不包含任何 UI 代码,使得最终打包体积更小。
  4. 更高的可扩展性: Headless 设计使得添加自定义功能或与其他库集成变得更加容易。

React Table v8 的核心思想是提供一系列强大的 Hooks 和辅助函数,你可以在你的 React 组件中使用它们来管理表格状态和获取渲染所需的数据和方法。

准备工作

在开始之前,请确保你具备以下条件:

  1. Node.js 和 npm/yarn: 用于管理项目依赖和运行开发服务器。
  2. React 基础知识: 理解 React 组件、Hooks (特别是 useState, useEffect, useMemo) 和 JSX。
  3. 一个 React 项目: 你可以使用 Create React App、Next.js、Vite 或任何其他你喜欢的 React 脚手架创建一个项目。

安装 React Table

首先,将 React Table 添加到你的项目中。打开终端,导航到你的项目目录,然后运行:

“`bash
npm install @tanstack/react-table

或者

yarn add @tanstack/react-table
“`

核心概念

理解 React Table v8 的核心概念是高效使用它的关键。

  1. useReactTable Hook: 这是 React Table 的核心入口点。你将表格的配置(数据、列定义、状态管理、启用的功能等)传递给这个 Hook,它会返回一个 table 实例。
  2. table 实例: 这个实例包含了渲染表格所需的一切:处理过的行数据、表头信息、以及用于交互(如排序、分页)的方法和状态。
  3. ColumnDef (列定义): 这是一个定义表格列结构和行为的对象数组。每一列至少需要一个 accessorKey (或 accessorFn) 来从行数据中提取值,以及一个 header 来定义表头显示内容。你可以为列定义 cell 来自定义单元格的渲染方式。
  4. 数据 (Data): 你的原始数据数组,通常是一个对象数组。
  5. 状态管理 (State Management): React Table 内部管理着许多状态(如排序状态、过滤状态、分页状态等)。你可以选择让 React Table 内部管理这些状态,也可以通过 stateonStateChange 选项将状态提升到你自己的 React 组件中进行控制。
  6. 功能 Hooks (Feature Hooks): React Table 的功能(如排序、过滤、分页)是通过不同的 “RowModel” Hooks 启用的,例如 getCoreRowModel (基础)、getSortedRowModel (排序)、getFilteredRowModel (过滤)、getPaginationRowModel (分页) 等。你需要在 useReactTable 中显式地引入这些 Hook 返回的函数来启用相应功能。

构建一个基础表格

让我们从创建一个最简单的表格开始。

1. 准备数据和列定义

假设我们有以下用户数据:

javascript
// data.js
export const defaultData = [
{ id: 1, firstName: 'Tanner', lastName: 'Linsley', age: 33, visits: 100, progress: 50, status: 'Married' },
{ id: 2, firstName: 'Kevin', lastName: 'Vandy', age: 27, visits: 200, progress: 75, status: 'Single' },
{ id: 3, firstName: 'John', lastName: 'Doe', age: 45, visits: 50, progress: 20, status: 'Complicated' },
{ id: 4, firstName: 'Jane', lastName: 'Doe', age: 42, visits: 150, progress: 90, status: 'Married' },
];

现在,定义表格的列。ColumnDef 是一个类型,你需要从 @tanstack/react-table 导入它(如果使用 TypeScript)。

“`javascript
// columns.js
import { createColumnHelper } from ‘@tanstack/react-table’;

// 假设你的数据类型是这样的
type Person = {
id: number;
firstName: string;
lastName: string;
age: number;
visits: number;
progress: number;
status: string;
};

const columnHelper = createColumnHelper(); // 使用 createColumnHelper 提供类型安全和智能提示

export const columns = [
columnHelper.accessor(‘firstName’, {
header: () => ‘First Name’, // 表头显示内容
cell: info => info.getValue(), // 单元格显示内容 (默认行为)
footer: info => info.column.id, // 可选的表尾
}),
columnHelper.accessor(row => row.lastName, { // 也可以使用 accessorFn
id: ‘lastName’, // 当使用 accessorFn 时,最好提供一个唯一的 id
header: () => Last Name, // 表头可以是 JSX
cell: info => {info.getValue()}, // 自定义单元格渲染
footer: info => info.column.id,
}),
columnHelper.accessor(‘age’, {
header: () => ‘Age’,
cell: info => info.renderValue(), // renderValue 是 getValue 的别名
footer: info => info.column.id,
}),
columnHelper.accessor(‘visits’, {
header: () => Visits,
footer: info => info.column.id,
}),
columnHelper.accessor(‘status’, {
header: ‘Status’, // header 可以是字符串
footer: info => info.column.id,
}),
columnHelper.accessor(‘progress’, {
header: ‘Profile Progress’,
footer: info => info.column.id,
}),
];
“`

注意:
* accessorKey: 直接对应数据对象中的键名。
* accessorFn: 一个函数,接收原始行数据,返回该单元格的值。当需要计算或组合字段时很有用。如果使用 accessorFn,强烈建议提供一个唯一的 id
* header: 定义表头单元格的内容。可以是一个字符串、一个函数返回字符串/JSX。
* cell: 定义数据单元格的内容。接收一个包含 getValue()renderValue()rowcolumn 等信息的对象,返回字符串/JSX。
* createColumnHelper: (可选,推荐 TypeScript 用户使用) 这是一个辅助函数,可以提供更好的类型推断和代码提示。

2. 创建表格组件

现在,在你的 React 组件中使用 useReactTable

“`jsx
// BasicTable.jsx
import React, { useState, useMemo } from ‘react’;
import {
useReactTable,
getCoreRowModel,
flexRender, // 重要的渲染辅助函数
} from ‘@tanstack/react-table’;
import { defaultData } from ‘./data’; // 导入数据
import { columns } from ‘./columns’; // 导入列定义
import ‘./table.css’; // 稍后添加一些基础样式

function BasicTable() {
// 推荐使用 useMemo 包装数据和列定义,避免不必要的重渲染
const data = useMemo(() => defaultData, []);
const tableColumns = useMemo(() => columns, []);

// 使用 useReactTable Hook
const table = useReactTable({
data, // 数据
columns: tableColumns, // 列定义
getCoreRowModel: getCoreRowModel(), // 核心行模型,必须启用
// debugTable: true, // 可选:在控制台输出调试信息
});

// table 实例包含了渲染所需的所有内容
// console.log(table); // 可以打印出来看看里面有什么

return (

{/ 表头 /}

{table.getHeaderGroups().map(headerGroup => (

{headerGroup.headers.map(header => (

))}

))}

    {/* 表体 */}
    <tbody>
      {table.getRowModel().rows.map(row => (
        <tr key={row.id}>
          {row.getVisibleCells().map(cell => (
            <td key={cell.id}>
              {flexRender( // 使用 flexRender 渲染单元格内容
                cell.column.columnDef.cell,
                cell.getContext()
              )}
            </td>
          ))}
        </tr>
      ))}
    </tbody>

    {/* 表尾 (可选) */}
    {/* <tfoot>
      {table.getFooterGroups().map(footerGroup => (
        <tr key={footerGroup.id}>
          {footerGroup.headers.map(header => (
            <th key={header.id} colSpan={header.colSpan}>
              {header.isPlaceholder
                ? null
                : flexRender(
                    header.column.columnDef.footer,
                    header.getContext()
                  )}
            </th>
          ))}
        </tr>
      ))}
    </tfoot> */}
  </table>
</div>

);
}

export default BasicTable;
“`

代码解释:

  • useMemo: 我们将 datacolumns 包装在 useMemo 中。这很重要,因为如果它们在每次渲染时都是新的引用,React Table 会认为输入发生了变化,从而触发不必要的计算。
  • useReactTable({...}): 初始化表格实例。目前只传入了 data, columnsgetCoreRowModel()
  • table.getHeaderGroups(): 获取表头行组。对于简单表格,通常只有一个行组。对于列分组(多级表头),会有多个。
  • headerGroup.headers: 获取当前行组中的所有表头单元格(Header 实例)。
  • header.isPlaceholder: 用于处理复杂的表头结构,简单表格中通常为 false
  • flexRender(content, props): 这是 React Table v8 的一个关键辅助函数。它负责智能地渲染 header, cell, footer 的内容。如果 content 是一个函数,flexRender 会调用它并传入 props (通过 header.getContext(), cell.getContext() 获取);如果 content 是字符串或 JSX,它会直接渲染。这使得列定义更加灵活。
  • header.getContext() / cell.getContext(): 获取传递给 header/cell 渲染函数的上下文信息,包含 table, column, row, cell, value 等。
  • table.getRowModel().rows: 获取经过处理(未来会包含排序、过滤后的行)的行数组 (Row 实例)。
  • row.getVisibleCells(): 获取当前行中所有可见的单元格 (Cell 实例)。

3. 添加基础样式 (table.css)

为了让表格看起来更清晰,添加一些简单的 CSS:

“`css
/ table.css /
.table-container {
padding: 1rem;
overflow-x: auto; / 保证小屏幕下可滚动 /
}

table {
border-collapse: collapse;
width: 100%;
border: 1px solid #ddd;
font-family: sans-serif;
}

th, td {
border: 1px solid #ddd;
padding: 8px 12px;
text-align: left;
}

th {
background-color: #f2f2f2;
font-weight: bold;
}

tbody tr:nth-child(even) {
background-color: #f9f9f9;
}

tbody tr:hover {
background-color: #eef;
}
“`

现在,在你的应用中渲染 BasicTable 组件,你应该能看到一个带有数据的基础 HTML 表格。

添加交互功能

React Table 的强大之处在于其模块化的功能。我们可以按需添加排序、过滤、分页等。

1. 添加排序 (Sorting)

要启用排序:

  1. 导入 getSortedRowModel:@tanstack/react-table 导入它。
  2. 添加到 useReactTable:getSortedRowModel: getSortedRowModel() 添加到 Hook 的配置中。
  3. 管理排序状态:
    • 使用 useState 来存储当前的排序状态 (sorting)。
    • sorting 状态和 onSortingChange 更新函数传递给 useReactTablestateonSortingChange 选项。
  4. 修改表头渲染:
    • <th> 元素上添加 onClick 事件,调用 header.column.getToggleSortingHandler() 来切换排序状态。
    • 检查 header.column.getCanSort() 确定列是否可排序。
    • 使用 header.column.getIsSorted() 来获取当前的排序状态('asc', 'desc', false)并显示相应的排序指示符(例如 ▲ 或 ▼)。

“`jsx
// EnhancedTable.jsx (在 BasicTable 基础上修改)
import React, { useState, useMemo } from ‘react’;
import {
useReactTable,
getCoreRowModel,
getSortedRowModel, // 1. 导入排序模型
flexRender,
SortingState, // 导入类型 (可选,用于 TS)
} from ‘@tanstack/react-table’;
import { defaultData } from ‘./data’;
import { columns } from ‘./columns’;
import ‘./table.css’;

function EnhancedTable() {
const data = useMemo(() => defaultData, []);
const tableColumns = useMemo(() => columns, []);

// 2. 管理排序状态
const [sorting, setSorting] = useState([]); // 初始无排序

const table = useReactTable({
data,
columns: tableColumns,
state: { // 3. 将状态传入 useReactTable
sorting,
},
onSortingChange: setSorting, // 4. 提供状态更新函数
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), // 5. 启用排序模型
// debugTable: true,
});

return (

{header.isPlaceholder
? null
: flexRender( // 使用 flexRender 渲染表头内容
header.column.columnDef.header,
header.getContext()
)}
{table.getHeaderGroups().map(headerGroup => (

{headerGroup.headers.map(header => (

))}

))}

{/ 表体渲染逻辑不变 /}
{table.getRowModel().rows.map(row => (

{row.getVisibleCells().map(cell => (

))}

))}

{header.isPlaceholder ? null : (


{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{/ 7. 显示排序指示符 /}
{{
asc: ‘ 🔼’,
desc: ‘ 🔽’,
}[header.column.getIsSorted() as string] ?? null}

)}

{flexRender(cell.column.columnDef.cell, cell.getContext())}

);
}

export default EnhancedTable;
“`

添加一个简单的 CSS 类来指示可点击:

css
/* table.css (补充) */
.cursor-pointer {
cursor: pointer;
}
.select-none {
user-select: none; /* 防止点击时选中文本 */
}

现在,点击表头应该可以按升序、降序或取消排序来对表格数据进行排序了。

2. 添加全局过滤 (Global Filtering)

全局过滤允许用户输入一个关键词,在所有列中搜索匹配项。

  1. 导入 getFilteredRowModel:@tanstack/react-table 导入。
  2. 添加到 useReactTable: 加入 getFilteredRowModel: getFilteredRowModel()
  3. 管理全局过滤状态:
    • 使用 useState 存储 globalFilter 字符串。
    • 将其传入 useReactTablestate 中,并提供 onGlobalFilterChange 更新函数。
  4. 添加过滤输入框: 在表格外部或上方创建一个 input 元素,将其 value 绑定到 globalFilter 状态,并在 onChange 事件中调用 setGlobalFilter

“`jsx
// EnhancedTable.jsx (继续修改)
import React, { useState, useMemo } from ‘react’;
import {
// … 其他导入
getFilteredRowModel, // 1. 导入过滤模型
} from ‘@tanstack/react-table’;
// … 其他代码

function EnhancedTable() {
// … data, tableColumns, sorting 状态

// 2. 管理全局过滤状态
const [globalFilter, setGlobalFilter] = useState(”);

const table = useReactTable({
data,
columns: tableColumns,
state: {
sorting,
globalFilter, // 3. 传入全局过滤状态
},
onSortingChange: setSorting,
onGlobalFilterChange: setGlobalFilter, // 4. 提供更新函数
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(), // 5. 启用过滤模型
// debugTable: true,
});

return (

{/ 6. 添加全局过滤输入框 /}

  <table>
    {/* 表格渲染逻辑不变 */}
    {/* ... thead ... */}
    {/* ... tbody ... */}
  </table>
  {/* 其他功能,如分页,可以放在这里 */}
</div>

);
}

export default EnhancedTable;
“`

现在,你应该有了一个搜索框,输入内容会实时过滤表格数据。

3. 添加分页 (Pagination)

对于大数据集,分页是必不可少的。

  1. 导入 getPaginationRowModel:@tanstack/react-table 导入。
  2. 添加到 useReactTable: 加入 getPaginationRowModel: getPaginationRowModel()
  3. 管理分页状态 (可选但推荐): React Table 默认会管理分页状态 (页码 pageIndex,每页大小 pageSize)。你也可以像 sorting 一样,使用 useState 管理 pagination 状态 ({ pageIndex, pageSize }) 并传入 stateonPaginationChange。这在你需要外部控制分页(例如服务器端分页)或自定义分页逻辑时非常有用。对于客户端分页,让 React Table 内部管理通常更简单。
  4. 添加分页控件: 在表格下方添加按钮(上一页、下一页、跳转到指定页)和信息显示(当前页/总页数)。
    • 使用 table.getCanPreviousPage()table.getCanNextPage() 来禁用/启用翻页按钮。
    • 调用 table.previousPage()table.nextPage() 来翻页。
    • 使用 table.setPageIndex(index) 跳转到指定页。
    • 使用 table.getState().pagination.pageIndex 获取当前页码(0-based)。
    • 使用 table.getPageCount() 获取总页数。
    • 使用 table.setPageSize(number) 设置每页显示的数量。

“`jsx
// EnhancedTable.jsx (继续修改)
import React, { useState, useMemo } from ‘react’;
import {
// … 其他导入
getPaginationRowModel, // 1. 导入分页模型
PaginationState, // 可选类型
} from ‘@tanstack/react-table’;
// … 其他代码

function EnhancedTable() {
// … data, tableColumns, sorting, globalFilter 状态

// — 如果需要手动控制分页状态 —
// const [pagination, setPagination] = useState({
// pageIndex: 0, // 初始页码
// pageSize: 5, // 初始每页大小
// });
// ——————————

const table = useReactTable({
data,
columns: tableColumns,
state: {
sorting,
globalFilter,
// pagination, // 如果手动控制则传入
},
onSortingChange: setSorting,
onGlobalFilterChange: setGlobalFilter,
// onPaginationChange: setPagination, // 如果手动控制则传入
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(), // 2. 启用分页模型
// debugTable: true,
// initialState: { pagination: { pageSize: 5 } }, // 或者通过 initialState 设置初始 pageSize
});

// 注意:即使不手动管理分页状态,也可以通过 table.getState().pagination 访问当前状态
const currentPageIndex = table.getState().pagination.pageIndex;
const currentPageSize = table.getState().pagination.pageSize;
const totalPages = table.getPageCount();

return (

{/ … 全局过滤输入框 … /}

{/ … thead … /}
{/ … tbody … /}

  {/* 3. 添加分页控件 */}
  <div className="pagination-controls" style={{ marginTop: '1rem', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
    <button onClick={() => table.setPageIndex(0)} disabled={!table.getCanPreviousPage()}>
      {'<<'} First
    </button>
    <button onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>
      {'<'} Previous
    </button>
    <span>
      Page{' '}
      <strong>
        {currentPageIndex + 1} of {totalPages}
      </strong>{' '}
    </span>
    <button onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>
      Next {'>'}
    </button>
    <button onClick={() => table.setPageIndex(totalPages - 1)} disabled={!table.getCanNextPage()}>
      Last {'>>'}
    </button>

    <span style={{ marginLeft: '1rem' }}>
      | Go to page:{' '}
      <input
        type="number"
        defaultValue={currentPageIndex + 1}
        onChange={e => {
          const page = e.target.value ? Number(e.target.value) - 1 : 0;
          // 确保页码在有效范围内
          const safePage = Math.max(0, Math.min(page, totalPages - 1));
          table.setPageIndex(safePage);
        }}
        style={{ width: '60px', padding: '2px 4px' }}
        min={1}
        max={totalPages}
      />
    </span>{' '}
    <select
      value={currentPageSize}
      onChange={e => {
        table.setPageSize(Number(e.target.value));
      }}
      style={{ padding: '4px 8px' }}
    >
      {[5, 10, 20, 50].map(size => (
        <option key={size} value={size}>
          Show {size}
        </option>
      ))}
    </select>
  </div>
  {/* 显示当前可见行数和总行数 (可选) */}
  {/* <div>Showing {table.getRowModel().rows.length} of {table.getPrePaginationRowModel().rows.length} total rows</div> */}
</div>

);
}

export default EnhancedTable;
“`

现在你的表格应该只显示指定数量的行,并提供了完整的客户端分页控制功能。

4. 添加行选择 (Row Selection)

允许用户选择表格中的一行或多行。

  1. 启用行选择:useReactTable 配置中添加 enableRowSelection: true (或者一个函数 (row) => boolean 来决定哪些行可选)。
  2. 管理选择状态:
    • 使用 useState 管理 rowSelection 状态 (一个记录选中行 ID 的对象,如 { 'rowId1': true, 'rowId2': true })。
    • 将其传入 stateonRowSelectionChange
  3. 添加选择控件 (Checkbox):
    • 表头 Checkbox: 在表头添加一个 Checkbox 用于全选/取消全选。
      • checked: table.getIsAllRowsSelected()
      • indeterminate: table.getIsSomeRowsSelected()
      • onChange: table.getToggleAllRowsSelectedHandler()
    • 行 Checkbox: 在每行添加一个 Checkbox 用于选择单行。
      • checked: row.getIsSelected()
      • disabled: !row.getCanSelect() (如果使用了 enableRowSelection 函数)
      • onChange: row.getToggleSelectedHandler()
  4. 定义行 ID (重要): 为了让行选择正常工作,React Table 需要知道如何唯一标识每一行。默认情况下,它会尝试使用 data[index] 作为 ID,但这在数据变化或分页时可能不稳定。强烈推荐通过 getRowId 选项提供一个函数来返回每行的唯一 ID。

“`jsx
// EnhancedTable.jsx (继续修改)
import React, { useState, useMemo } from ‘react’;
import {
// … 其他导入
RowSelectionState, // 可选类型
} from ‘@tanstack/react-table’;
// … 其他代码

// — 定义一个 Indeterminate Checkbox 组件 (处理不确定状态) —
function IndeterminateCheckbox({
indeterminate,
className = ”,
…rest
}: { indeterminate?: boolean } & React.HTMLProps) {
const ref = React.useRef(null!);

React.useEffect(() => {
if (typeof indeterminate === ‘boolean’) {
ref.current.indeterminate = !rest.checked && indeterminate;
}
}, [ref, indeterminate, rest.checked]);

return (

);
}
// ———————————————————

function EnhancedTable() {
const data = useMemo(() => defaultData, []);
// 1. 为列定义添加一个用于放置 Checkbox 的新列
const selectionColumns = useMemo(() => [
// 选择列
{
id: ‘select’,
header: ({ table }) => ( // 表头 Checkbox

),
cell: ({ row }) => ( // 行 Checkbox

),
size: 50, // 给选择列一个较小的固定宽度
},
…columns, // 其他原始列
], []);

// … sorting, globalFilter 状态

// 2. 管理行选择状态
const [rowSelection, setRowSelection] = useState({});

const table = useReactTable({
data,
columns: selectionColumns, // 使用包含选择列的列定义
state: {
sorting,
globalFilter,
rowSelection, // 3. 传入选择状态
},
onSortingChange: setSorting,
onGlobalFilterChange: setGlobalFilter,
onRowSelectionChange: setRowSelection, // 4. 提供更新函数
enableRowSelection: true, // 5. 启用行选择
// enableMultiRowSelection: false, // 可选:禁用按住 Shift 多选
// enableSubRowSelection: true, // 可选:用于树状数据/分组
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
// 6. 定义行的唯一 ID (非常重要!)
getRowId: row => String(row.id), // 假设每行数据都有一个唯一的 ‘id’ 属性
// debugTable: true,
// debugHeaders: true,
// debugColumns: true,
});

return (

{/ … 全局过滤输入框 … /}

{/ 表头渲染逻辑不变,会自动包含新的选择列 /}

{table.getHeaderGroups().map(headerGroup => (

{headerGroup.headers.map(header => (

))}

))}

{/ 表体渲染逻辑不变,会自动包含新的选择列 /}

{table.getRowModel().rows.map(row => (


{row.getVisibleCells().map(cell => (

))}

))}

{/ 应用 size /}
{/ … 表头渲染内容(包含排序逻辑) … /}
{header.isPlaceholder ? null : (


{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{{
asc: ‘ 🔼’,
desc: ‘ 🔽’,
}[header.column.getIsSorted() as string] ?? null}

)}

{/ 应用 size /}
{flexRender(cell.column.columnDef.cell, cell.getContext())}

{/ … 分页控件 … /}

   {/* 显示选中的行数和 ID (可选) */}
  <div style={{marginTop: '1rem'}}>
    <p>{Object.keys(rowSelection).length} row(s) selected.</p>
    {/* <pre>{JSON.stringify(rowSelection, null, 2)}</pre> */}
  </div>
</div>

);
}

export default EnhancedTable;
“`

现在,你的表格第一列应该出现了 Checkbox,允许用户进行单选、多选和全选操作。getRowId 的正确设置对于选择状态在数据变化(如排序、过滤)后依然保持正确至关重要。IndeterminateCheckbox 组件用于正确显示全选框的“部分选中”状态。

其他功能与考量

React Table 还提供了许多其他强大的功能:

  • 列过滤 (Column Filtering): 与全局过滤类似,但可以为特定列添加过滤输入框。需要 getFilteredRowModel,并通过列定义中的 filterFn (内置或自定义过滤逻辑) 和 enableColumnFilter 来控制。
  • 列排序 (Column Ordering): 允许用户通过拖放来改变列的顺序。需要管理 columnOrder 状态。
  • 列显隐 (Column Visibility): 允许用户选择显示或隐藏某些列。需要管理 columnVisibility 状态。
  • 列固定 (Column Pinning): 可以将列固定在左侧或右侧,使其在水平滚动时保持可见。需要管理 columnPinning 状态。
  • 列分组 (Column Grouping): 创建多级表头。在列定义中嵌套 columns 数组即可。
  • 行分组 (Row Grouping): 根据某些列的值对行进行分组。需要 getGroupedRowModelgetExpandedRowModel,并管理 groupingexpanded 状态。
  • 行展开 (Row Expansion): 创建可展开/折叠的行,用于显示子行或详细信息。需要 getExpandedRowModel 并管理 expanded 状态。
  • 单元格/行/列大小调整 (Resizing): 允许用户拖动来调整列宽或行高。需要 columnResizeMode 和管理 columnSizing 状态。
  • 服务器端数据处理: 对于非常大的数据集,你可能需要在服务器上进行排序、过滤和分页。React Table 对此有良好支持,你需要将相关的状态 (sorting, filtering, pagination) 设置为 manual,并在状态变化时向服务器发送请求,然后用服务器返回的数据和元信息 (如总行数) 更新表格。

性能优化:

  • Memoization: 始终使用 useMemo 包装传递给 useReactTabledatacolumns,避免不必要的重计算。
  • 虚拟化 (Virtualization): 对于包含成百上千甚至更多行的表格,渲染所有 DOM 节点会非常慢。这时应使用虚拟滚动库(如 @tanstack/react-virtual, react-window, react-virtualized)来只渲染视口内可见的行。React Table 设计上与这些库兼容性良好,但需要你手动集成。

TypeScript 支持:

React Table v8 提供了出色的 TypeScript 支持。使用 @tanstack/react-table 提供的类型(如 ColumnDef, Table, Row, Cell, SortingState 等)可以极大地提高开发体验和代码健壮性。createColumnHelper<TData>() 是编写类型安全的列定义的好帮手。

总结

React Table (v8 / TanStack Table) 是一个功能极其丰富且高度灵活的 React 表格解决方案。其 Headless UI 的设计哲学赋予了开发者前所未有的控制力,可以自由定制表格的标记、样式和行为,同时保持核心逻辑的强大和高效。

通过组合使用 useReactTable Hook 和各种功能模型 Hooks (getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel 等),并管理相应的状态,我们可以轻松构建出满足各种复杂需求的交互式数据表格。虽然初看起来配置项较多,但这种模块化设计使得我们可以按需引入功能,保持代码的清晰和高效。

掌握 React Table 不仅能让你构建出色的数据展示界面,更能加深对 React Hooks 和状态管理的理解。无论你是需要一个简单的排序表格,还是一个支持复杂交互、处理海量数据的网格,React Table 都值得你深入学习和使用。


发表评论

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

滚动至顶部