想直接查看实现代码?请参考以下示例:
TanStack Table 对客户端分页和服务端分页都提供了完善的支持。本指南将引导您了解在表格中实现分页的不同方式。
使用客户端分页意味着您获取的 data 将包含表格的所有行数据,表格实例会在前端处理分页逻辑。
客户端分页通常是使用 TanStack Table 实现分页的最简单方式,但对于非常大的数据集可能不太实用。
不过,很多人低估了客户端能处理的数据量。如果您的表格最多只有几千行数据,客户端分页仍然是一个可行的选择。TanStack Table 设计用于处理数万行数据,在分页、筛选、排序和分组方面都能保持良好的性能。官方分页示例加载了 10 万行数据,仍然表现良好(尽管只有少数几列)。
每个用例都不同,取决于表格的复杂度、列数、每条数据的大小等因素。需要关注的主要瓶颈是:
如果不确定,可以先从客户端分页开始,随着数据增长再切换到服务端分页。
另一种处理大数据集的方式是不分页,而是在同一页面上渲染所有行,但仅使用浏览器资源渲染视口中可见的行。这种策略通常称为“虚拟化”或“窗口化”。TanStack 提供了一个虚拟化库 TanStack Virtual,可以与 TanStack Table 配合使用。虚拟化和分页在 UI/UX 上各有优缺点,请根据您的用例选择最适合的方式。
如果想利用 TanStack Table 内置的客户端分页功能,首先需要传入分页行模型。
import { useReactTable, getCoreRowModel, getPaginationRowModel } from '@tanstack/react-table';
//...
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(), //加载客户端分页代码
});
import { useReactTable, getCoreRowModel, getPaginationRowModel } from '@tanstack/react-table';
//...
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(), //加载客户端分页代码
});
如果确定需要使用服务端分页,以下是实现方式。
服务端分页不需要分页行模型,但如果为其他需要分页的共享组件提供了该模型,仍可以通过将 manualPagination 选项设为 true 来关闭客户端分页。设置 manualPagination: true 会让表格实例在底层使用 table.getPrePaginationRowModel 行模型,并假设传入的 data 已经是分页后的数据。
除非明确告知,否则表格实例无法知道后端总共有多少行/页数据。通过提供 rowCount 或 pageCount 表格选项,可以让表格实例知道总页数。如果提供 rowCount,表格实例会根据 rowCount 和 pageSize 内部计算 pageCount。也可以直接提供 pageCount(如果已知)。如果不知道总页数,可以传入 -1,但此时 getCanNextPage 和 getCanPreviousPage 行模型函数将始终返回 true。
import { useReactTable, getCoreRowModel, getPaginationRowModel } from '@tanstack/react-table';
//...
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
// getPaginationRowModel: getPaginationRowModel(), //服务端分页不需要
manualPagination: true, //关闭客户端分页
rowCount: dataQuery.data?.rowCount, //传入总行数,表格会计算总页数(如果不提供 pageCount)
// pageCount: dataQuery.data?.pageCount, //或者直接传入 pageCount
});
import { useReactTable, getCoreRowModel, getPaginationRowModel } from '@tanstack/react-table';
//...
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
// getPaginationRowModel: getPaginationRowModel(), //服务端分页不需要
manualPagination: true, //关闭客户端分页
rowCount: dataQuery.data?.rowCount, //传入总行数,表格会计算总页数(如果不提供 pageCount)
// pageCount: dataQuery.data?.pageCount, //或者直接传入 pageCount
});
注意:设置 manualPagination: true 会让表格实例假设传入的 data 已经是分页后的数据。
无论使用客户端分页还是手动服务端分页,都可以使用内置的 pagination 状态和 API。
pagination 状态是一个包含以下属性的对象:
可以像管理表格实例中的其他状态一样管理 pagination 状态。
import { useReactTable, getCoreRowModel, getPaginationRowModel } from '@tanstack/react-table';
//...
const [pagination, setPagination] = useState({
pageIndex: 0, //初始页码
pageSize: 10, //默认每页大小
});
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onPaginationChange: setPagination, //当内部 API 修改分页状态时更新
state: {
//...
pagination,
},
});
import { useReactTable, getCoreRowModel, getPaginationRowModel } from '@tanstack/react-table';
//...
const [pagination, setPagination] = useState({
pageIndex: 0, //初始页码
pageSize: 10, //默认每页大小
});
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onPaginationChange: setPagination, //当内部 API 修改分页状态时更新
state: {
//...
pagination,
},
});
如果不需要在自身作用域中管理 pagination 状态,但需要设置不同的 pageIndex 和 pageSize 初始值,可以使用 initialState 选项。
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
initialState: {
pagination: {
pageIndex: 2, //自定义初始页码
pageSize: 25, //自定义默认每页大小
},
},
});
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
initialState: {
pagination: {
pageIndex: 2, //自定义初始页码
pageSize: 25, //自定义默认每页大小
},
},
});
注意:不要同时将 pagination 状态传递给 state 和 initialState 选项。state 会覆盖 initialState,只需使用其中之一。
除了对手动服务端分页有用的 manualPagination、pageCount 和 rowCount 选项(已在上文讨论),还有一个表格选项值得了解。
默认情况下,当发生影响分页的状态变化(如 data 更新、筛选条件变化、分组变化等)时,pageIndex 会自动重置为 0。当 manualPagination 为 true 时,此行为会自动禁用,但可以通过显式为 autoResetPageIndex 表格选项分配布尔值来覆盖。
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
autoResetPageIndex: false, //关闭自动重置页码
});
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
autoResetPageIndex: false, //关闭自动重置页码
});
但请注意,如果关闭 autoResetPageIndex,可能需要自行添加逻辑来处理 pageIndex 的重置,以避免显示空页面。
有几个分页表格实例 API 可用于连接分页 UI 组件。
注意:部分 API 是 v8.13.0 新增的。
<Button
onClick={() => table.firstPage()}
disabled={!table.getCanPreviousPage()}
>
{'<<'}
</Button>
<Button
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
{'<'}
</Button>
<Button
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
{'>'}
</Button>
<Button
onClick={() => table.lastPage()}
disabled={!table.getCanNextPage()}
>
{'>>'}
</Button>
<select
value={table.getState().pagination.pageSize}
onChange={e => {
table.setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
{pageSize}
</option>
))}
</select>
<Button
onClick={() => table.firstPage()}
disabled={!table.getCanPreviousPage()}
>
{'<<'}
</Button>
<Button
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
{'<'}
</Button>
<Button
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
{'>'}
</Button>
<Button
onClick={() => table.lastPage()}
disabled={!table.getCanNextPage()}
>
{'>>'}
</Button>
<select
value={table.getState().pagination.pageSize}
onChange={e => {
table.setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
{pageSize}
</option>
))}
</select>
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.