跳至主要内容

[note] Tanstack Table

常用 API

/**
* Header Groups and Header
**/
table.getHeaderGroups();
table.getFlatHeaders();

<thead>
{table.getHeaderGroups().map((headerGroup) => {
return (
<tr key={headerGroup.id}>
{headerGroup.headers.map(
(
header // map over the headerGroup headers array
) => (
<th key={header.id} colSpan={header.colSpan}>
{flexRender(header.column.columnDef.header, header.getContext())}
</th>
)
)}
</tr>
);
})}
</thead>;

/**
* Row
*
* table.getXXXRowModel().rows;
* table.getXXXRowModel().flatRows;
* table.getXXXRowModel().rowsById['row-id']; // 取得某一列的資料
**/

table.getRowModel().rows; // 取得所有 rows

table.getRow("rowId"); // 取得某一列的資料

row.getValue("columnName"); // 取得某一 row 中某個 column 的值,如果 value 是 undefined 會直接回傳
row.renderValue("columnName"); // 取得某一 row 中某個 column 的值,如果 value 是 undefined 會直接用 renderFallbackValue
row.original.columnName; // 取得 data 最原始的值(還沒被 accessor 轉換後的值)

<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>{/* ... */}</tr>
))}
</tbody>;

/**
* Column
**/
table.getColumn("column-name"); // 取得欄位資訊
table.getAllColumns()
table.getAllFlatColumns();
header.column;
cell.column;

/**
* Cell
**/
cell.getValue("columnName"); // 是 row.getValue() 的捷徑
cell.renderValue("columnName"); // 是 row.renderValue() 的捷徑
cell.row.original.firstName; // 可以透過 cell 得到 row
row.getAllCells();
row.getVisibleCells();

<tr>
{row.getVisibleCells().map((cell) => {
return (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
);
})}
</tr>;

// Filtered and Selected
table.getFilteredRowModel().rows;
table.getSelectedRowModel().rows;
table.getFilteredSelectedRowModel().rows;

// Pagination
table.previousPage();
table.nextPage();
table.getCanPreviousPage();
table.getCanNextPage();

/**
* Table State
**/
table.getState();
table.getState().rowSelection;

ColumnDef

[
columnHelper.accessor((row) => row.name, {
size: 200,
header: "Name(Age)",
cell: ({ getValue, row, table }) => {
// 拿 table 提供的 meta
const currentHour = table.options.meta?.currentHour;

// 使用 row 拿其他 column 的資料
const age = row.getValue<ApiData["age"]>("age");

// 用 getValue 可以直接拿這個 column 在這個 row(也就是這個 cell)的資料
const name = getValue<ApiData['name']>();

// 拿 raw data 中的資料
const name_original = row.original.name;
const age_original = row.original.age;
return (
<div>
{name}({age})
</div>
); // 渲染時顯示組合格式
},
footer: ({ table }) => {
// 使用 table.getCoreRowModel() 拿 row 來加總某個 column 的值
const totalRev = table
.getCoreRowModel()
.rows.reduce(
(sum, row) =>
sum + (row.getValue<ApiData["revenue"]>("revenue") ?? 0),
0
);
},
}),
];

Core Concepts

Columns

accessorFn 和 cell 的差別

雖然在簡單的顯示場景中兩者效果相同,但 accessorFn 會影響表格的所有數據操作功能(例如,排序、篩選、分組),而 cell 純粹是視覺層面的。理解這個差異對於構建功能完整的表格很重要。

{
accessorFn: (row) => row.name, // 只用「名稱」來排序
header: "Name(Age)",
cell: ({ row }) => {
const name = row.original.name;
const age = row.original.age;
return <div>{name}({age})</div>; // 渲染時顯示組合格式
},
}

Tips

固定部分欄寬,其餘自己延伸

  • <table> 本身要套用 .table-fixedtable-layout: fixed;
  • 要自動延伸欄寬的欄位設成 enableResizing: false
  • 使用 cell.column.getCanResize() 判斷,如果它可以 resize,就拿 cell.column.getSize() 當做寬度,如果不能 resize,就用 auto
const Example = () => {
const columns = useMemo(
() => [
columnHelper.accessor('timestamp', {
size: 200,
header: 'Time',
cell: ({ getValue }) =>
format(new Date(getValue()), 'yyyy-MM-dd HH:mm:ss'),
}),
columnHelper.accessor('created_by', {
size: 250,
header: 'Updated By',
cell: ({ getValue }) => (
<Typography fontWeight={600}>{getValue()}</Typography>
),
}),
columnHelper.accessor('ui_readable_changes', {
enableResizing: false,
header: 'Changes',
}),
]
, [])

return (
<Table className="table-fixed">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead
key={header.id}
style={{
width: header.column.getCanResize()
? header.getSize()
: 'auto',
}}
className="font-semibold"
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
style={{
width: cell.column.getCanResize()
? cell.column.getSize()
: 'auto',
}}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
)
}