数据指南

数据指南

表格从您的数据开始。您的列定义和行将取决于数据的形状。TanStack Table 具有一些 TypeScript 功能,可以帮助您以出色的类型安全体验创建表格代码的其余部分。如果您正确设置了数据和类型,TanStack Table 将能够推断数据的形状并强制正确创建列定义。

TypeScript

使用 TanStack Table 包不需要 TypeScript... 但是 TanStack Table 的编写和组织方式使得您获得的超棒 TypeScript 体验感觉就像是该库的主要卖点之一。如果您不使用 TypeScript,您将错过许多出色的自动完成和类型检查功能,这些功能既可以加快您的开发时间,又可以减少代码中的错误数量。

TypeScript 泛型

对 TypeScript 泛型是什么以及它们如何工作有一个基本的了解将有助于您更好地理解本指南,但随着您的学习,应该很容易掌握。官方的 TypeScript 泛型文档 可能对那些还不熟悉 TypeScript 的人有所帮助。

定义数据类型

data 是一个对象数组,将转换为表格的行。数组中的每个对象代表一行数据(在正常情况下)。如果您正在使用 TypeScript,我们通常会为数据的形状定义一个类型。此类型用作所有其他表格、列、行和单元格实例的泛型类型。此泛型在 TanStack Table 类型和 API 的其余部分��通常称为 TData

例如,如果我们有一个表格,以如下数组形式显示用户列表:

json
[
  {
    "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) 类型:

ts
//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 类型即可。

ts
//注意:数据需要一个“稳定”的引用以防止无限重渲染
const data: User[] = []
//或
const [data, setData] = React.useState<User[]>([])
//或
const data = ref<User[]>([]) //vue
//等等...
//注意:数据需要一个“稳定”的引用以防止无限重渲染
const data: User[] = []
//或
const [data, setData] = React.useState<User[]>([])
//或
const data = ref<User[]>([]) //vue
//等等...

深层键控数据

如果您的数据不是一个漂亮的扁平对象数组,那也没关系!当您开始定义列时,有一些策略可以访问访问器中深层嵌套的数据。

如果您的 data 看起来像这样:

json
[
  {
    "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,
    }
  }
]

您可以像这样定义一个类型:

ts
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 在列定义中访问数据。

ts
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 数据中的“键”通常可以是任何内容,但键中的任何句点都将被解释为深层键并导致错误。

嵌套子行数据

如果您正在使用展开功能,那么在数据中包含嵌套子行是很常见的。这会导致一个有点不同的递归类型。

因此,如果您的数据如下所示:

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",
  }
]

您可以像这样定义一个类型:

ts
type User = {
  firstName: string
  lastName: string
  subRows?: User[] //不必称为“subRows”,可以称为任何名称
}
type User = {
  firstName: string
  lastName: string
  subRows?: User[] //不必称为“subRows”,可以称为任何名称
}

其中 subRows 是一个可选的 User 对象数组。这在展开指南中有更详细的讨论。

为数据提供“稳定”引用

您传递给表格实例的 data 数组 必须 具有“稳定”引用,以防止导致无限重渲染的错误(尤其是在 React 中)。

这将取决于您使用的框架适配器,但在 React 中,您通常应该使用 React.useStateReact.useMemo 或类似方法来确保 datacolumns 表格选项都具有稳定引用。

tsx
const fallbackData = []

export default function MyComponent() {
  //✅ 正确:这不会导致无限循环的重渲染,因为 `columns` 是一个稳定的引用
  const columns = useMemo(() => [
    // ...
  ], []);

  //✅ 正确:这不会导致无限循环的重渲染,因为 `data` 是一个稳定的引用
  const [data, setData] = useState(() => [
    // ...
  ]);

  // 列和数据在稳定引用中定义,不会导致无限循环!
  const table = useReactTable({
    columns,
    data ?? fallbackData, //也最好使用在组件外部定义的后备数组(稳定引用)
  });

  return <table>...</table>;
}
const fallbackData = []

export default function MyComponent() {
  //✅ 正确:这不会导致无限循环的重渲染,因为 `columns` 是一个稳定的引用
  const columns = useMemo(() => [
    // ...
  ], []);

  //✅ 正确:这不会导致无限循环的重渲染,因为 `data` 是一个稳定的引用
  const [data, setData] = useState(() => [
    // ...
  ]);

  // 列和数据在稳定引用中定义,不会导致无限循环!
  const table = useReactTable({
    columns,
    data ?? fallbackData, //也最好使用在组件外部定义的后备数组(稳定引用)
  });

  return <table>...</table>;
}

React.useStateReact.useMemo 并不是为数据提供稳定引用的唯一方法。您还可以在组件外部定义数据,或使用第三方状态管理库,如 Redux、Zustand 或 TanStack Query。

要避免的主要问题是在与 useReactTable 调用相同的范围内定义 data 数组。这将导致 data 数组在每次渲染时重新定义,从而导致无限循环的重渲染。

tsx
export default function MyComponent() {
  //😵 错误:这将导致无限循环的重渲染,因为 `columns` 在每次渲染时都重新定义为一个新数组!
  const columns = [
    // ...
  ];

  //😵 错误:这将导致无限循环的重渲染,因为 `data` 在每次渲染时都重新定义为一个新数组!
  const data = [
    // ...
  ];

  //❌ 列和数据在与 `useReactTable` 相同的范围内定义,没有稳定引用,将导致无限循环!
  const table = useReactTable({
    columns,
    data ?? [], //❌ 也不好,因为后备数组在每次渲染时都会重新创建
  });

  return <table>...</table>;
}
export default function MyComponent() {
  //😵 错误:这将导致无限循环的重渲染,因为 `columns` 在每次渲染时都重新定义为一个新数组!
  const columns = [
    // ...
  ];

  //😵 错误:这将导致无限循环的重渲染,因为 `data` 在每次渲染时都重新定义为一个新数组!
  const data = [
    // ...
  ];

  //❌ 列和数据在与 `useReactTable` 相同的范围内定义,没有稳定引用,将导致无限循环!
  const table = useReactTable({
    columns,
    data ?? [], //❌ 也不好,因为后备数组在每次渲染时都会重新创建
  });

  return <table>...</table>;
}

TanStack Table 如何转换数据

稍后,在这些文档的其他部分,您将看到 TanStack Table 如何处理您传递给表格的 data,并生成用于创建表格的行和单元格对象。您传递给表格的 data 永远不会被 TanStack Table 修改,但行和单元格中的实际值可能会被列定义中的访问器或行模型(如分组或聚合)执行的其他功能转换。

TanStack Table 可以处理多少数据?

信不信由你,TanStack Table 实际上是为了在客户端处理可能多达数十万行数据而构建的。显然,这并非总是可能的,具体取决于每列数据的大小和列数。但是,排序、筛选、分页和分组功能都是在考虑大型数据集性能的情况下构建的。

构建数据网格的开发人员的默认心态是为大型数据集实现服务器端分页、排序和筛选。这通常仍然是一个好主意,但许多开发人员低估了在现代浏览器和适当优化的情况下,客户端实际可以处理多少数据。如果您的表格永远不会超过几千行,您可能可以利用 TanStack Table 中的客户端功能,而不是在服务器上自己实现它们。当然,在承诺让 TanStack Table 的客户端功能处理大型数据集之前,您应该使用实际数据对其进行测试,以查看其性能是否足以满足您的需求。

这在分页指南中有更详细的讨论。