表格始于你的数据。你的列定义和行将取决于你的数据结构。TanStack Table 包含一些 TypeScript 功能,可帮助你以出色的类型安全体验创建表格代码的其余部分。如果正确设置了数据和类型,TanStack Table 将能够推断出你的数据结构,并强制你的列定义得到正确创建。
使用 TanStack Table 包不需要 TypeScript... ***但是*** TanStack Table 的编写和组织方式使得它所提供的出色的 TypeScript 体验感觉像是该库的主要卖点之一。如果你不使用 TypeScript,你将错过许多出色的自动完成和类型检查功能,它们将缩短你的开发时间并减少代码中的错误数量。
对 TypeScript 泛型是什么以及它们如何工作有一个基本了解将有助于你更好地理解本指南,但随着学习的进行,应该很容易掌握。对于那些还不熟悉 TypeScript 的人来说,官方的 TypeScript 泛型文档 可能会有帮助。
data 是一个对象数组,它将被转换成表格的行。数组中的每个对象代表一行数据(在正常情况下)。如果你使用 TypeScript,我们通常会为数据结构定义一个类型。此类型用作所有其他表格、列、行和单元格实例的泛型类型。此泛型通常在 TanStack Table 类型和 API 的其余部分中被称为 TData。
例如,如果我们有一个表格,该表格显示了如下的数组中的用户列表
[
{
"firstName": "Tanner",
"lastName": "Linsley",
"age": 33,
"visits": 100,
"progress": 50,
"status": "Married"
},
{
"firstName": "Kevin",
"lastName": "Vandy",
"age": 27,
"visits": 200,
"progress": 100,
"status": "Single"
}
]
[
{
"firstName": "Tanner",
"lastName": "Linsley",
"age": 33,
"visits": 100,
"progress": 50,
"status": "Married"
},
{
"firstName": "Kevin",
"lastName": "Vandy",
"age": 27,
"visits": 200,
"progress": 100,
"status": "Single"
}
]
那么我们可以像这样定义一个 User (TData) 类型
//TData
type User = {
firstName: string
lastName: string
age: number
visits: number
progress: number
status: string
}
//TData
type User = {
firstName: string
lastName: string
age: number
visits: number
progress: number
status: string
}
然后我们可以用这个类型来定义我们的 data 数组,然后 TanStack Table 将能够智能地为我们推断出后续的列、行、单元格等的类型。这是因为 data 类型被明确地定义为 TData 泛型类型。你传递给 data 表格选项的任何内容都将成为表格实例其余部分的 TData 类型。只需确保在稍后定义列定义时,它们使用与 data 类型相同的 TData 类型。
//note: data needs a "stable" reference in order to prevent infinite re-renders
const data: User[] = []
//or
const [data, setData] = React.useState<User[]>([])
//or
const data = ref<User[]>([]) //vue
//etc...
//note: data needs a "stable" reference in order to prevent infinite re-renders
const data: User[] = []
//or
const [data, setData] = React.useState<User[]>([])
//or
const data = ref<User[]>([]) //vue
//etc...
如果你的数据不是一个漂亮的扁平对象数组,那也没关系!当你开始定义列时,有策略可以访问访问器中深度嵌套的数据。
如果你的 data 看起来像这样
[
{
"name": {
"first": "Tanner",
"last": "Linsley"
},
"info": {
"age": 33,
"visits": 100,
}
},
{
"name": {
"first": "Kevin",
"last": "Vandy"
},
"info": {
"age": 27,
"visits": 200,
}
}
]
[
{
"name": {
"first": "Tanner",
"last": "Linsley"
},
"info": {
"age": 33,
"visits": 100,
}
},
{
"name": {
"first": "Kevin",
"last": "Vandy"
},
"info": {
"age": 27,
"visits": 200,
}
}
]
你可以这样定义一个类型
type User = {
name: {
first: string
last: string
}
info: {
age: number
visits: number
}
}
type User = {
name: {
first: string
last: string
}
info: {
age: number
visits: number
}
}
你将能够通过 `accessorKey` 中的点表示法或简单地使用 `accessorFn` 在你的列定义中访问数据。
const columns = [
{
header: 'First Name',
accessorKey: 'name.first',
},
{
header: 'Last Name',
accessorKey: 'name.last',
},
{
header: 'Age',
accessorFn: row => row.info.age,
},
//...
]
const columns = [
{
header: 'First Name',
accessorKey: 'name.first',
},
{
header: 'Last Name',
accessorKey: 'name.last',
},
{
header: 'Age',
accessorFn: row => row.info.age,
},
//...
]
这将在 列定义指南 中进行更详细的讨论。
注意:JSON 数据中的“键”通常可以是任何内容,但键中的任何句点都将被解释为深度键,并会导致错误。
如果你使用展开功能,数据中包含嵌套的子行是很常见的。这会导致一个稍微不同的递归类型。
所以如果你的数据看起来像这样
[
{
"firstName": "Tanner",
"lastName": "Linsley",
"subRows": [
{
"firstName": "Kevin",
"lastName": "Vandy",
},
{
"firstName": "John",
"lastName": "Doe",
"subRows": [
//...
]
}
]
},
{
"firstName": "Jane",
"lastName": "Doe",
}
]
[
{
"firstName": "Tanner",
"lastName": "Linsley",
"subRows": [
{
"firstName": "Kevin",
"lastName": "Vandy",
},
{
"firstName": "John",
"lastName": "Doe",
"subRows": [
//...
]
}
]
},
{
"firstName": "Jane",
"lastName": "Doe",
}
]
你可以这样定义一个类型
type User = {
firstName: string
lastName: string
subRows?: User[] //does not have to be called "subRows", can be called anything
}
type User = {
firstName: string
lastName: string
subRows?: User[] //does not have to be called "subRows", can be called anything
}
其中 subRows 是一个可选的 User 对象数组。这将在 展开指南 中进行更详细的讨论。
传递给表格实例的 data 数组 ***必须*** 具有“稳定”引用,以防止导致无限重新渲染(尤其是在 React 中)的错误。
这将取决于你使用的框架适配器,但在 React 中,你应该经常使用 React.useState、React.useMemo 或类似的工具来确保 data 和 columns 表格选项都具有稳定的引用。
const fallbackData = []
export default function MyComponent() {
//✅ GOOD: This will not cause an infinite loop of re-renders because `columns` is a stable reference
const columns = useMemo(() => [
// ...
], []);
//✅ GOOD: This will not cause an infinite loop of re-renders because `data` is a stable reference
const [data, setData] = useState(() => [
// ...
]);
// Columns and data are defined in a stable reference, will not cause infinite loop!
const table = useReactTable({
columns,
data ?? fallbackData, //also good to use a fallback array that is defined outside of the component (stable reference)
});
return <table>...</table>;
}
const fallbackData = []
export default function MyComponent() {
//✅ GOOD: This will not cause an infinite loop of re-renders because `columns` is a stable reference
const columns = useMemo(() => [
// ...
], []);
//✅ GOOD: This will not cause an infinite loop of re-renders because `data` is a stable reference
const [data, setData] = useState(() => [
// ...
]);
// Columns and data are defined in a stable reference, will not cause infinite loop!
const table = useReactTable({
columns,
data ?? fallbackData, //also good to use a fallback array that is defined outside of the component (stable reference)
});
return <table>...</table>;
}
React.useState 和 React.useMemo 不是为数据提供稳定引用的唯一方法。你也可以在组件外部定义数据,或使用第三方状态管理库,如 Redux、Zustand 或 TanStack Query。
主要需要避免的是在与 useReactTable 调用相同的范围内定义 data 数组。这会导致 data 数组在每次渲染时重新定义,从而导致无限次的重新渲染循环。
export default function MyComponent() {
//😵 BAD: This will cause an infinite loop of re-renders because `columns` is redefined as a new array on every render!
const columns = [
// ...
];
//😵 BAD: This will cause an infinite loop of re-renders because `data` is redefined as a new array on every render!
const data = [
// ...
];
//❌ Columns and data are defined in the same scope as `useReactTable` without a stable reference, will cause infinite loop!
const table = useReactTable({
columns,
data ?? [], //❌ Also bad because the fallback array is re-created on every render
});
return <table>...</table>;
}
export default function MyComponent() {
//😵 BAD: This will cause an infinite loop of re-renders because `columns` is redefined as a new array on every render!
const columns = [
// ...
];
//😵 BAD: This will cause an infinite loop of re-renders because `data` is redefined as a new array on every render!
const data = [
// ...
];
//❌ Columns and data are defined in the same scope as `useReactTable` without a stable reference, will cause infinite loop!
const table = useReactTable({
columns,
data ?? [], //❌ Also bad because the fallback array is re-created on every render
});
return <table>...</table>;
}
稍后,在本文档的其他部分,你将看到 TanStack Table 如何处理你传递给表格的 data,并生成用于创建表格的行和单元格对象。你传递给表格的 data 永远不会被 TanStack Table 变异,但行和单元格中的实际值可能会被列定义中的访问器或 行模型(如分组或聚合)执行的其他功能所转换。
令人难以置信的是,TanStack Table 最初是为了在客户端处理成千上万行数据而设计的。当然,这并非总是可能,具体取决于每列数据的大小和列数。然而,排序、过滤、分页和分组功能都注重在大数据集的性能。
开发者构建数据网格的默认思路是为大型数据集实现服务器端分页、排序和过滤。这通常仍然是个好主意,但许多开发者低估了现代浏览器和正确优化后客户端实际上可以处理多少数据。如果你的表格的行数永远不会超过几千行,你也许可以利用 TanStack Table 的客户端功能,而不是自己编写服务器端实现。在决定让 TanStack Table 的客户端功能处理你的大型数据集之前,你应该用你的实际数据进行测试,看看它的性能是否足够满足你的需求。
这将在 分页指南 中进行更详细的讨论。
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。