表格始于您的数据。您的列定义和行将取决于数据的结构。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
}
}
您将能够使用访问器键中的点表示法或仅使用 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 新闻。每周一免费发送给超过 100,000 名开发者。