关于 tanstack-table
为使用 TanStack Table v8 的开发者提供构建无头数据表的指南与模板,涵盖服务端数据模式、Cloudflare Workers 集成和大型数据集性能处理。
主要功能
- 017 种数据表模板
- 02服务端分页、筛选与排序模式
- 03Cloudflare D1 与 Workers 集成
- 04TanStack Virtual 虚拟化
- 05列状态与筛选控制
- 06常见问题排查指南
适用场景
- 01实现 API 驱动的表格分页与筛选
- 02将表格连接到 Cloudflare D1
- 03虚拟化大型客户端数据表
- 04从其他表格库迁移到 TanStack Table v8
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ secondsky/ claude-skills --skill tanstack-table安装后在 Claude Code 中输入/tanstack-table确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/cloudflare-d1-examples.mdcommon-errors.mdfeature-controls.mdperformance-virtualization.mdquery-integration.mdserver-side-patterns.mdtemplates/basic-client-table.tsxcolumn-configuration.tsxcontrolled-table-state.tsxd1-database-example.tsxpackage.jsonserver-paginated-table.tsxshadcn-styled-table.tsxvirtualized-large-dataset.tsx详细介绍
TanStack Table 技能
使用 TanStack Table v8 构建适用于生产环境的无头数据表,针对服务端模式和 Cloudflare Workers 集成进行优化。
何时使用此技能
提及以下内容时会自动触发:
- “数据表”或“datagrid”
- “服务端分页”或“服务端筛选”
- “TanStack Table”或“React Table”
- “包含大型数据集的表格”
- “通过 API 分页/筛选/排序”
- “Cloudflare D1 表集成”
- “虚拟化表格”或“大型列表性能”
以下情况请使用此技能:
- 构建带分页、筛选或排序功能的数据表
- 实现服务端表格功能(由 API 驱动)
- 将表格与 TanStack Query 集成以获取数据
- 处理需要虚拟化的大型数据集(1000 行以上)
- 将表格连接到 Cloudflare D1 数据库
- 需要无预设 UI 的无头表格逻辑
- 从其他表格库迁移到 TanStack Table v8
此技能提供的内容
1. 生产环境模板(7 个)
- 基础客户端表格 - 使用本地数据的简单表格
- 服务端分页表格 - 使用 TanStack Query 的 API 驱动分页
- D1 数据库集成 - Cloudflare D1 + Workers API + 表格
- 列配置模式 - 类型安全的列定义
- 受控表格状态 - 列可见性、固定、排序、模糊/全局筛选、行选择
- 大型数据集虚拟化 - 使用 TanStack Virtual 优化性能
- shadcn/ui 样式表格 - 与 Tailwind v4 + shadcn 集成
2. 服务端模式
- 使用 API 后端进行分页
- 使用查询参数筛选
- 使用数据库查询排序
- 状态管理(页码、筛选条件、排序)
- URL 同步
- TanStack Query 协调
3. Cloudflare 集成
- D1 数据库查询模式
- 用于表格数据的 Workers API 端点
- 在 SQL 中进行分页 + 筛选 + 排序
- 绑定设置(wrangler.jsonc)
- 客户端集成模式
4. 性能优化
- 使用 TanStack Virtual 进行虚拟化
- 渲染大型数据集(10k 行以上)
- 高效利用内存的模式
- useVirtualizer() 集成
5. 功能控制与 UX
- 列可见性切换和固定(冻结列)
- 列排序和默认尺寸
- 全局 + 模糊搜索及分面筛选器
- 行选择和行固定模式
- 受控状态检查清单,避免性能回退
6. 错误预防
记录并预防 6 种以上常见问题:
- 服务端状态管理混乱
- TanStack Query 集成错误(查询键协调)
- 使用 API 后端时的列筛选
- 手动排序设置错误
- URL 状态同步问题
- 大型数据集性能问题
- 过度控制表格状态(columnSizingInfo)导致额外渲染
快速开始
安装
# Core table library
bun add @tanstack/react-table@latest
# Optional: For virtualization (1000+ rows)
bun add @tanstack/react-virtual@latest
# Optional: For fuzzy/global search
bun add @tanstack/match-sorter-utils@latest
最新验证版本(截至 2025-12-09):
@tanstack/react-table:v8.21.3(稳定版)@tanstack/react-virtual:v3.13.23@tanstack/match-sorter-utils:v8.21.3(用于模糊筛选)
**React 支持:**适用于 React 16.8+ 至 React 19;不支持 React Compiler。
基础客户端表格
import { useReactTable, getCoreRowModel, ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
interface User {
id: string
name: string
email: string
}
const columns: ColumnDef<User>[] = [
{ accessorKey: 'id', header: 'ID' },
{ accessorKey: 'name', header: 'Name' },
{ accessorKey: 'email', header: 'Email' },
]
function UsersTable() {
// CRITICAL: Memoize data and columns to prevent infinite re-renders
const data = useMemo<User[]>(() => [
{ id: '1', name: 'Alice', email: '[email protected]' },
{ id: '2', name: 'Bob', email: '[email protected]' },
], [])
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(), // Required
})
return (
<table>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id}>
{header.isPlaceholder ? null : header.column.columnDef.header}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(row => (
<tr key={row.id}>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>
{cell.renderValue()}
</td>
))}
</tr>
))}
</tbody>
</table>
)
}
服务端模式(推荐用于大型数据集)
模式 1:使用 TanStack Query 的服务端分页
Cloudflare Workers API 端点:
// src/routes/api/users.ts
import { Env } from '../../types'
export async function onRequestGet(context: { request: Request; env: Env }) {
const url = new URL(context.request.url)
const page = Number(url.searchParams.get('page')) || 0
const pageSize = Number(url.searchParams.get('pageSize')) || 20
const offset = page * pageSize
// Query D1 database
const { results, meta } = await context.env.DB.prepare(`
SELECT id, name, email, created_at
FROM users
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`).bind(pageSize, offset).all()
// Get total count for pagination
const countResult = await context.env.DB.prepare(`
SELECT COUNT(*) as total FROM users
`).first<{ total: number }>()
return Response.json({
data: results,
pagination: {
page,
pageSize,
total: countResult?.total || 0,
pageCount: Math.ceil((countResult?.total || 0) / pageSize),
},
})
}
使用 TanStack Query 的客户端表格:
import { useReactTable, getCoreRowModel, PaginationState } from '@tanstack/react-table'
import { useQuery } from '@tanstack/react-query'
import { useState } from 'react'
function ServerPaginatedTable() {
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 20,
})
// TanStack Query fetches data
const { data, isLoading } = useQuery({
queryKey: ['users', pagination.pageIndex, pagination.pageSize],
queryFn: async () => {
const response = await fetch(
`/api/users?page=${pagination.pageIndex}&pageSize=${pagination.pageSize}`
)
return response.json()
},
})
// TanStack Table manages display
const table = useReactTable({
data: data?.data ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
// Server-side pagination config
manualPagination: true, // CRITICAL: Tell table pagination is manual
pageCount: data?.pagination.pageCount ?? 0,
state: { pagination },
onPaginationChange: setPagination,
})
if (isLoading) return <div>Loading...</div>
return (
<div>
<table>{/* render table */}</table>
{/* Pagination controls */}
<div>
<button
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</button>
<span>
Page {table.getState().pagination.pageIndex + 1} of{' '}
{table.getPageCount()}
</span>
<button
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</button>
</div>
</div>
)
}
模式 2:服务端筛选
支持筛选的 API:
export async function onRequestGet(context: { request: Request; env: Env }) {
const url = new URL(context.request.url)
const search = url.searchParams.get('search') || ''
const { results } = await context.env.DB.prepare(`
SELECT * FROM users
WHERE name LIKE ? OR email LIKE ?
LIMIT 20
`).bind(`%${search}%`, `%${search}%`).all()
return Response.json({ data: results })
}
客户端:
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const { data } = useQuery({
queryKey: ['users', columnFilters],
queryFn: async () => {
const search = columnFilters.find(f => f.id === 'search')?.value || ''
return fetch(`/api/users?search=${search}`).then(r => r.json())
},
})
const table = useReactTable({
data: data?.data ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
manualFiltering: true, // CRITICAL: Server handles filtering
state: { columnFilters },
onColumnFiltersChange: setColumnFilters,
})
大型数据集虚拟化
对于 1000 行以上的数据,请使用 TanStack Virtual,仅渲染可见行:
import { useVirtualizer } from '@tanstack/react-virtual'
import { useRef } from 'react'
function VirtualizedTable() {
const tableContainerRef = useRef<HTMLDivElement>(null)
const table = useReactTable({
data: largeDataset, // 10k+ rows
columns,
getCoreRowModel: getCoreRowModel(),
})
const { rows } = table.getRowModel()
// Virtualize rows
const rowVirtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => tableContainerRef.current,
estimateSize: () => 50, // Row height in px
overscan: 10, // Render 10 extra rows for smooth scrolling
})
return (
<div ref={tableContainerRef} style={{ height: '600px', overflow: 'auto' }}>
<table style={{ height: `${rowVirtualizer.getTotalSize()}px` }}>
<thead>{/* header */}</thead>
<tbody>
{rowVirtualizer.getVirtualItems().map(virtualRow => {
const row = rows[virtualRow.index]
return (
<tr
key={row.id}
style={{
position: 'absolute',
transform: `translateY(${virtualRow.start}px)`,
width: '100%',
}}
>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>{cell.renderValue()}</td>
))}
</tr>
)
})}
</tbody>
</table>
</div>
)
}
常见错误与解决方案
错误 1:无限重新渲染
**问题:**表格无限重新渲染,浏览器冻结。
**原因:**每次渲染时,data 或 columns 的引用都会变化。
**解决方案:**始终使用 useMemo 或 useState:
// ❌ BAD: New array reference every render
function Table() {
const data = [{ id: 1 }] // Creates new array!
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
}
// ✅ GOOD: Stable reference
function Table() {
const data = useMemo(() => [{ id: 1 }], []) // Stable
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
}
// ✅ ALSO GOOD: Define outside component
const data = [{ id: 1 }]
function Table() {
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
}
错误 2:TanStack Query + 表格状态不同步
问题: Query 重新获取数据,但分页状态未同步,导致数据过时。
解决方案: 将所有表格状态都包含在查询键中:
// ❌ BAD: Missing pagination in query key
const { data } = useQuery({
queryKey: ['users'], // Doesn't include page!
queryFn: () => fetch(`/api/users?page=${pagination.pageIndex}`).then(r => r.json())
})
// ✅ GOOD: Complete query key
const { data } = useQuery({
queryKey: ['users', pagination.pageIndex, pagination.pageSize, columnFilters, sorting],
queryFn: () => {
const params = new URLSearchParams({
page: pagination.pageIndex.toString(),
pageSize: pagination.pageSize.toString(),
// ... filters, sorting
})
return fetch(`/api/users?${params}`).then(r => r.json())
}
})
错误 3:服务端功能无法正常工作
问题: 分页、筛选或排序不会触发 API 调用。
解决方案: 将 manual* 标志设为 true:
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
// CRITICAL: Tell table these are server-side
manualPagination: true,
manualFiltering: true,
manualSorting: true,
pageCount: serverPageCount, // Must provide total page count
})
错误 4:TypeScript 提示“Cannot Find Module”,无法找到列辅助工具
问题: 导入 createColumnHelper 时出错。
解决方案: 从正确的路径导入:
// ❌ BAD: Wrong path
import { createColumnHelper } from '@tanstack/table-core'
// ✅ GOOD: Correct path
import { createColumnHelper } from '@tanstack/react-table'
// Usage for type-safe columns
const columnHelper = createColumnHelper<User>()
const columns = [
columnHelper.accessor('name', {
header: 'Name',
cell: info => info.getValue(), // Fully typed!
}),
]
错误 5:服务端排序无法正常工作
问题: 点击排序表头不会更新数据。
解决方案: 在查询键和 API 调用中包含排序状态:
const [sorting, setSorting] = useState<SortingState>([])
const { data } = useQuery({
queryKey: ['users', pagination, sorting], // Include sorting
queryFn: async () => {
const sortParam = sorting[0]
? `&sortBy=${sorting[0].id}&sortOrder=${sorting[0].desc ? 'desc' : 'asc'}`
: ''
return fetch(`/api/users?page=${pagination.pageIndex}${sortParam}`).then(r => r.json())
}
})
const table = useReactTable({
data: data?.data ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
manualSorting: true,
state: { sorting },
onSortingChange: setSorting,
})
错误 6:大型数据集性能不佳
问题: 数据行数达到 1000+ 时,表格运行缓慢或卡顿。
解决方案: 使用虚拟化(参见上面的示例),或实现服务端分页。
与现有技能集成
与 tanstack-query 技能集成
TanStack Table + TanStack Query 是推荐的模式:
// Query handles data fetching + caching
const { data, isLoading } = useQuery({
queryKey: ['users', tableState],
queryFn: fetchUsers,
})
// Table handles display + interactions
const table = useReactTable({
data: data?.data ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
})
与 cloudflare-d1 技能集成
// Cloudflare Workers API (from cloudflare-d1 skill patterns)
export async function onRequestGet({ env }: { env: Env }) {
const { results } = await env.DB.prepare('SELECT * FROM users LIMIT 20').all()
return Response.json({ data: results })
}
// Client-side table consumes D1 data
const { data } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json())
})
与 tailwind-v4-shadcn 技能集成
将 shadcn/ui Table 组件与 TanStack Table 逻辑结合使用:
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'
function StyledTable() {
const table = useReactTable({ /* config */ })
return (
<Table>
<TableHeader>
{table.getHeaderGroups().map(headerGroup => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map(header => (
<TableHead key={header.id}>
{header.column.columnDef.header}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.map(row => (
<TableRow key={row.id}>
{row.getVisibleCells().map(cell => (
<TableCell key={cell.id}>
{cell.renderValue()}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
)
}
最佳实践
1. 始终对数据和列进行记忆化
const data = useMemo(() => [...], [dependencies])
const columns = useMemo(() => [...], [])
2. 对大型数据集使用服务端处理
- 客户端处理:少于 1000 行
- 服务端处理:1000+ 行或数据频繁变化
3. 让查询键与表格状态保持一致
queryKey: ['resource', pagination, filters, sorting]
4. 提供加载状态
if (isLoading) return <TableSkeleton />
if (error) return <ErrorMessage error={error} />
5. 使用列辅助工具确保类型安全
const columnHelper = createColumnHelper<YourType>()
const columns = [
columnHelper.accessor('field', { /* fully typed */ })
]
6. 对大型客户端表格进行虚拟化
if (data.length > 1000) {
// Use TanStack Virtual (see example above)
}
7. 只控制所需的状态
- 在需要持久化或同步时,将
sorting、pagination、filters、visibility、pinning、order、selection保存在受控状态中。 - 除非需要持久化拖动状态,否则避免控制
columnSizingInfo;它会触发频繁更新,可能影响性能。
模板参考
~/.claude/skills/tanstack-table/templates/ 中提供的所有模板:
- package.json - 依赖项和版本
- basic-client-table.tsx - 简单的客户端表格
- server-paginated-table.tsx - 使用 Query 实现服务端分页
- d1-database-example.tsx - Cloudflare D1 集成
- column-configuration.tsx - 类型安全的列模式
- controlled-table-state.tsx - 可见性、固定、排序、模糊/全局筛选、选择
- virtualized-large-dataset.tsx - 使用 Virtual 提升性能
- shadcn-styled-table.tsx - Tailwind v4 + shadcn UI 样式
参考文档
~/.claude/skills/tanstack-table/references/ 中的深入指南:
- server-side-patterns.md - 使用 API 实现分页、筛选和排序
- query-integration.md - 与 TanStack Query 协调
- cloudflare-d1-examples.md - Workers + D1 完整示例
- performance-virtualization.md - TanStack Virtual 指南
- common-errors.md - 记录的全部 6+ 个问题及解决方案
- feature-controls.md - 受控状态、可见性、固定、排序、模糊/全局筛选、选择
何时加载参考文档
Claude 应根据用户需求建议加载这些参考文件:
在以下情况下加载 references/common-errors.md:
- 用户遇到无限重新渲染或表格冻结
- Query 数据未随分页状态变化而同步
- 服务端功能(分页/筛选/排序)未触发 API 调用
- 列辅助工具导入出现 TypeScript 错误
- 排序状态变化未更新 API 调用
- 客户端处理 1000+ 行时出现性能问题
- 遇到 6 个已记录问题中的任何错误消息
在以下情况下加载 references/server-side-patterns.md:
- 用户询问如何使用 API 后端实现分页
- 需要使用后端查询参数实现筛选
- 实现与数据库查询相关的排序
- 为表格数据构建 Cloudflare Workers 或任何 API 端点
- 协调表格状态(页码、筛选、排序)与服务端调用
- 询问 manualPagination、manualFiltering 或 manualSorting 标志
在以下情况下加载 references/query-integration.md:
- 协调 TanStack Table 与 TanStack Query
- 查询键与表格状态同步问题
- 分页/筛选/排序更改时的重新获取模式
- 结合表格状态构建查询键
- 服务端表格中的过期数据问题
在以下情况下加载 references/cloudflare-d1-examples.md:
- 为表格数据构建 Cloudflare Workers API 端点
- 编写带分页/筛选的 D1 数据库查询
- 需要完整的端到端 Cloudflare 集成示例
- 表格功能的 SQL 查询模式(LIMIT/OFFSET、WHERE、ORDER BY)
- 为 D1 + 表格配置 wrangler.jsonc 绑定
在以下情况下加载 references/performance-virtualization.md:
- 在客户端处理包含 1000 行以上的数据集
- TanStack Virtual 集成问题
- 内存高效的渲染模式
- useVirtualizer() 钩子的用法
- 大型表格的性能优化
- 行虚拟化或滚动性能相关问题
在以下情况下加载 references/feature-controls.md:
- 需要列可见性、固定或排序控件
- 构建工具栏(全局搜索、切换控件)或将状态同步到 URL/localStorage
- 实现模糊/全局搜索或分面筛选
- 设置行选择/固定或受控分页/排序
Token 效率
不使用此技能:
- ~8,000 个 token:研究 v8 变更、服务端模式和 Query 集成
- 会遇到 3-4 个常见错误
- 总计需要 30-45 分钟
使用此技能:
- ~3,500 个 token:直接使用模板,避免错误
- 0 个错误(文档中记录的所有问题均可避免)
- 需要 10-15 分钟
**节省:**约 55-65% 的 token,约 70% 的时间
生产环境验证
测试环境:
- React 19.2
- Vite 7.3
- TypeScript 5.9
- Cloudflare Workers(Wrangler 4.0)
- TanStack Query v5.101.4(tanstack-query skill)
- Tailwind v4 + shadcn/ui(tailwind-v4-shadcn skill)
技术栈兼容性:
- ✅ Cloudflare Workers + 静态资源
- ✅ Cloudflare D1 数据库
- ✅ TanStack Query 集成
- ✅ React 19.2+ 服务端组件
- ✅ TypeScript 严格模式
- ✅ Vite 7.3+ 构建优化
延伸阅读
- 官方文档: https://tanstack.com/table/latest
- TanStack Virtual: https://tanstack.com/virtual/latest
- GitHub: https://github.com/TanStack/table
- Cloudflare D1 Skill:
~/.claude/skills/cloudflare-d1/ - TanStack Query Skill:
~/.claude/skills/tanstack-query/
最后更新: 2025-12-09 技能版本: 1.1.0 库版本: @tanstack/react-table v8.21.3