Lzh on GitHub

数据指南

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

TypeScript

使用 TanStack Table 包 不需要 TypeScript…… 但是,TanStack Table 的编写和组织方式使其获得的卓越 TypeScript 体验,感觉就像是该库的主要卖点之一。如果你不使用 TypeScript,你将错过大量出色的 自动补全 (autocompletion)类型检查 (type-checking) 功能,这些功能将 加快你的开发时间并减少代码中的 bug 数量

TypeScript 泛型 (Generics)

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

那么我们可以像这样定义一个 User (TData) 类型:

// TData
type User = {
  firstName: string
  lastName: string
  age: number
  visits: number
  progress: number
  status: string
}

然后我们可以使用此类型定义我们的 data 数组,之后 TanStack Table 将能够智能地为我们的列、行、单元格等推断出大量类型。这是因为 data 类型实际上被定义为 TData 泛型类型。你传递给 data 表格选项的任何内容都将成为表格实例其余部分的 TData 类型。只需确保你以后定义列定义时,使用与 data 类型相同的 TData 类型。

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

深度嵌套键的数据 (Deep Keyed Data)

如果你的数据不是一个扁平的对象数组,那也没关系!当你开始定义列时,有多种策略可以在 访问器 (accessors) 中访问深度嵌套的数据。

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

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

你将能够通过在 accessorKey 中使用点表示法或直接使用 accessorFn 来访问列定义中的数据。

const columns = [
  {
    header: 'First Name',
    accessorKey: 'name.first'
  },
  {
    header: 'Last Name',
    accessorKey: 'name.last'
  },
  {
    header: 'Age',
    accessorFn: (row) => row.info.age
  }
  // ...
]

这在 列定义指南 中有更详细的讨论。

注意:你的 JSON 数据中的 “键” 通常可以是任何内容,但键中的任何句点都将被解释为深度键并导致错误。

嵌套子行数据 (Nested Sub-Row Data)

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

因此,如果你的数据看起来像这样:

[
  {
    "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[] // 不必叫 "subRows",可以叫任何名字
}

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

为数据提供“稳定”引用

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

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

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 数组在每次渲染时都被重新定义,从而导致无限重渲染循环。

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 改变 (mutate),但行和单元格中的实际值可能会被列定义中的 访问器 (accessors)行模型 (row models) 执行的其他功能(如分组或聚合)进行 转换 (transformed)

TanStack Table 能处理多少数据?

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

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

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