mcpField
D
data-table

data-table

github.com/virtuoso-dev开发148 次安装今天更新
代码仓库

关于 data-table

@virtuoso.dev/data-table 是面向 React 应用的虚拟化数据表库,支持灵活列配置及本地或远程数据。它提供无头包和 shadcn/ui 封装,并支持分组行与可配置表格状态。

主要功能

  1. 01虚拟化行与列
  2. 02本地及远程数据模型
  3. 03分组行与固定列
  4. 04调整、重排及隐藏列
  5. 05持久化表格与模型状态
  6. 06无头包和 shadcn/ui 选项

适用场景

  1. 01渲染大型 React 数据表
  2. 02构建由 API 提供数据的表格
  3. 03允许用户自定义列布局
  4. 04跨会话保留表格偏好设置

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/virtuoso-dev/skills --skill data-table

安装后在 Claude Code 中输入/data-table确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
4.grouped-rows.md
5.state-persistence.md
6.controlling-the-table.md
references/1.installation/
01.shadcn.md
02.headless.md
references/2.data-model/
01.local-data-model.md
02.remote-data-model.md
03.row-keys.md
index.md
references/3.columns/
01.defining-columns.md
02.cell-and-header-renderers.md
03.column-groups.md
04.sticky-columns.md
05.column-visibility.md
06.column-resizing.md
07.column-reordering.md
08.runtime-columns.md
09.column-layout.md
index.md
references/7.customization/
01.styling.md
02.replacing-internals.md
03.empty-and-loading-states.md
04.scroll-containers.md
05.ambient-context.md
06.header-slots.md
07.shadcn-wrapper.md
08.card-mode.md
references/8.examples/
01.basic-table.md
03.grouped-data.md
04.sticky-columns.md

详细介绍

@virtuoso.dev/data-table

一款虚拟化 React 数据表(行和列),支持分组行、固定列、列宽调整/重新排序/显示控制、状态持久化和远程数据。它是针对表格类问题的 TableVirtuoso 后继方案:无需传入行渲染器,只需传入数据源(模型),并通过 JSX 声明列。

安装:两种方式

Shadcn(预设样式封装) — 适用于采用 shadcn/ui 约定的项目:

npx shadcn@latest add petyosi/react-virtuoso/data-table

这会在 @/components/ui/data-table 安装带样式的封装,并导出 DataTable、DataTableColumn、DataTableColumnHeader、DataTableCell。 可选的功能 UI 部件是单独的注册项:

npx shadcn@latest add petyosi/react-virtuoso/data-table-resize-handle
npx shadcn@latest add petyosi/react-virtuoso/data-table-sort-header-button

注册项名称使用连字符。安装后的导入路径仍是嵌套结构(@/components/ui/data-table/column-resize、@/components/ui/data-table/column-sort)。不要运行 data-table/column-resize 或 data-table/column-sort 这类路径形式的 shadcn 命令;shadcn 解析的是注册项名称,而非导入路径。

无头模式 — 适用于自定义设计系统:

npm install @virtuoso.dev/data-table

导入结构样式(@import '@virtuoso.dev/data-table/styles.css'),并使用无样式的 VirtuosoDataTable、Column、ColumnHeader、Cell。安装前先确认哪种方式适合项目;对于采用 Tailwind/shadcn 的代码库,shadcn 封装是更快捷的起步方式。

最简示例(shadcn 封装)

import { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'
import { localModel } from '@virtuoso.dev/data-table'

const model = localModel({ data: products })

export default function App() {
  return (
    <DataTable model={model} style={{ height: 360 }}>
      <DataTableColumn field="name">
        <DataTableColumnHeader>Product</DataTableColumnHeader>
        <DataTableCell className="font-medium">{({ cellValue }) => String(cellValue)}</DataTableCell>
      </DataTableColumn>
    </DataTable>
  )
}

列使用 JSX,而不是配置对象。field 同时是行数据查找键和列的公开标识符(用于显示控制、重新排序和持久化)。与所有 Virtuoso 组件一样,表格需要设置实际高度。

应将 field 和 id 视为稳定的列标识,而不是面向用户的标签。为每个可见列显式添加 DataTableColumnHeader。这对仅用于显示的列(如 actions)尤其重要:应使用 Actions 这类可见标签,除非产品有意采用仅图标/无表头的列;此时应使用 sr-only 文本来保证无障碍访问。

如果表格需要填满页面、面板或卡片中的剩余高度,请使用经过测量的弹性列,而不是固定像素高度。从经过测量的容器到表格之间的每个弹性祖先元素都需要设置 min-h-0;非表格元素应设置 shrink-0;表格应作为可增长的子元素,并设置 style={{ height: '100%' }}:

<section className="flex h-full min-h-0 flex-col">
  <PageHeader className="shrink-0" />
  <div className="flex min-h-0 flex-1 flex-col gap-3">
    <Toolbar className="shrink-0" />
    <DataTable className="min-h-0 flex-1" model={model} style={{ height: '100%' }}>
      {/* columns */}
    </DataTable>
  </div>
</section>

这不是必需的。如果父级没有确定的高度,请继续使用固定高度(style={{ height: 360 }}),或有意选择 useWindowScroll/customScrollParent。

列宽由表格通过表头测量来控制。将 w-*、min-w-* 和 basis-* 等基础宽度类设置在 DataTableColumnHeader 上,而不是 DataTableCell 上。单元格会在依据表头测量值确定尺寸的轨道内渲染;单元格上的宽度工具类可能会使正文内容超出这些轨道,并导致列在较窄宽度下重叠。使用单元格的 className 设置排版、对齐、内边距、截断和颜色。对于复杂的单元格内容,应在内部包装元素上设置 min-w-0,而不是加宽单元格。

可增长的表头具有独立的测量和呈现区域。表格会先测量表头的固有尺寸,然后根据 grow 添加多余宽度。如果同一个被测量的元素也随着渲染后的列拉伸,ResizeObserver 就会将增长后的宽度报告为下一个基础宽度,导致列在视口变窄时无法收缩。

  • 使用 DataTableColumnHeader.className 设置固有表面的样式:固定或最小基础宽度、内边距、排版,以及在该基础宽度内的对齐方式。
  • 使用 DataTableColumnHeader.containerProps 设置渲染列范围内的绘制和交互样式:背景、悬停/焦点状态、轮廓、盒阴影、光标、ARIA 属性和事件处理器。
  • 不要在 containerProps 中设置宽度、flex、外边距、内边距、边框、display 或 justify-* 样式。表格负责外部几何布局;在此处设置布局样式可能会裁切内容,并移动 HeaderEnd 或 HeaderEdge 插槽。要设置全宽分隔线,请使用 box-shadow 或 outline。
  • containerProps 上的事件会接收来自排序按钮、调整大小控件、重新排序控件及其他嵌套 UI 的冒泡事件。请在嵌套控件中阻止事件传播,或在运行整个表头的操作前添加容器处理器的判断。
<DataTableColumn field="name" grow={1}>
  <DataTableColumnHeader className="min-w-72" containerProps={{ className: 'cursor-pointer hover:bg-muted/50 focus-within:outline' }}>
    Name
  </DataTableColumnHeader>
  <DataTableCell>{NameCell}</DataTableCell>
</DataTableColumn>

不要在 className 上使用 w-full 或 flex 增长来绘制整个渲染轨道。这些样式会按设计在固有测量边界内生效;请将全宽视觉样式放在 containerProps 上。需要让控件与渲染列边缘对齐时,请使用 HeaderEnd。

构建表格时,请根据所显示的数据选择固定列或增长列:

  • 省略 grow,将紧凑列保持为固定宽度:ID、slug、复选框、操作菜单、图标按钮、状态标签、徽章、版本、计数、日期、时间戳,以及简短的枚举或元数据列。
  • 使用 DataTableColumn grow={number} 将文本较多的列设为增长列:名称、标题、描述、摘要、消息、备注、路径,以及其他增加水平空间有助于浏览或减少截断的内容。
  • 使用正的有限增长值。grow={0} 等同于省略 grow;除非生成的配置结构需要显式值,否则固定列优先省略该属性。
  • 根据对当前表格的具体了解来决定。name 列通常会增长,但类似代码且较短的名称可以设为固定宽度;label 列通常是固定宽度,但用户编写的标签可以设为增长列。
  • 不要让每一列都增长。grow 可避免紧凑列吸收剩余空间,让实用的文本列获得所需空间。

示例:

<DataTableColumn field="name" grow={1}>
  <DataTableColumnHeader className="min-w-72">Name</DataTableColumnHeader>
  <DataTableCell>{NameCell}</DataTableCell>
</DataTableColumn>

<DataTableColumn field="description" grow={3}>
  <DataTableColumnHeader className="min-w-80">Description</DataTableColumnHeader>
  <DataTableCell>{DescriptionCell}</DataTableCell>
</DataTableColumn>

<DataTableColumn id="actions">
  <DataTableColumnHeader className="w-16 justify-center">Actions</DataTableColumnHeader>
  <DataTableCell>{ActionsCell}</DataTableCell>
</DataTableColumn>

选择数据模型

情况模型
行数据在内存中;在客户端筛选/排序/分组localModel({ data, pipeline?, actions?, groups? })
基于 API,已知总数(获取数据时显示占位行)remoteModel 使用偏移量模式(defaultOffsetViewportHandler);获取数据的函数返回 { rows, totalCount }
基于 API,使用游标分页/无限追加remoteModel 使用追加模式(defaultAppendViewportHandler);获取数据的函数返回 { rows }

使用惰性初始化将模型保存在 useState 中——const [model] = useState(() => localModel({ data }))。不要使用 useMemo;React 可能会丢弃记忆化的值,而模型实例必须保留。对于静态的单例表,也可以将模型放在模块作用域中。

本地筛选/排序/分组通过命名阶段管道运行:声明 pipeline: ['filter', 'sort'] 和 actions,然后使用 model.send({ action: 'filter', payload }) 分发操作。请参阅 local-data-model 和 local-filter-sort-group example。

对于远程排序/筛选/搜索控件,请将操作负载保存在模型中,而不要在 React 状态中镜像保存。使用 initialActions 设置默认值,通过 model.send() 或 dispatchModelAction$ 分发变更,并读取 modelActionState$ 来显示控件的激活状态。当 modelStatePersistenceAdapter() 恢复已保存的操作状态时,这一点尤为重要。

只要行可能会重新排序(排序、筛选、远程更新),就应提供 computeRowKey={({ data }) => data.id}——否则行会重新挂载并丢失本地状态。

列功能

  • 固定列: <DataTableColumn field="name" sticky="left" />(或 "right");多个固定列会依次堆叠。
  • 可见性: 通过 visible={false} 声明式设置,或在运行时使用 @virtuoso.dev/data-table/column-visibility 中的 setColumnVisibility$ / columnVisibilityState$。
  • 调整大小: 在 HeaderEdge 插槽中挂载 ResizeHandle;也可通过 @virtuoso.dev/data-table/column-resize 中的 resizeColumn$ 以编程方式调整。
  • 重新排序: 在 HeaderStart 中放置 ReorderGrip,并在 HeaderOverlay 中放置 ReorderDropZone;也可通过 @virtuoso.dev/data-table/column-reorder 中的 reorderColumns$ 以编程方式重新排序。
  • 表头插槽: HeaderStart(标签前)、HeaderEnd(标签后)、HeaderEdge(固定在列边界处)、HeaderOverlay(覆盖表头)——请参阅 header-slots。
  • 分组行: 将 groups: [{ index, level }] 与 data 一同传入,并使用 GroupHeaderCell 渲染表头——请参阅 grouped-rows。

状态持久化:挂载 <DataTableStatePersistence adapters={[...]} storageKey="my-table" />,并传入来自功能子路径的适配器(columnVisibilityPersistenceAdapter()、columnOrderPersistenceAdapter()、columnWidthPersistenceAdapter()、modelStatePersistenceAdapter())。请参阅 state-persistence。

从外部控制表格

表格状态保存在内部响应式引擎中,该包有意导出单元格(可读取的状态,以 $ 结尾)和流(操作),以便进行远程控制——状态不会提升到 props 中:

import { scrollLocation$, scrollToRow$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'

const engineRef = useEngineRef()
const scrollToRow = useRemotePublisher(scrollToRow$, engineRef)
const location = useRemoteCellValue(scrollLocation$, engineRef)

<DataTable engineRef={engineRef} model={model}>...</DataTable>
<button onClick={() => scrollToRow({ index: 100, align: 'start' })}>Go to row 100</button>

对于距离表格较远的 UI,传入 engineId="orders-table",并使用相同的钩子和字符串 ID。实用节点:scrollToRow$、scrollIntoView$、setColumnVisibility$、dispatchModelAction$(操作);scrollLocation$、columns$、columnVisibilityState$、modelActionState$、loadingState$(状态)。请参阅 控制表格。

自定义

  • 样式通过包装组件上的 className 和语义化数据属性设置——切勿将 data-testid 用作样式钩子。
  • 对于列标题,使用 className 设置固有尺寸和内容布局,使用 containerProps 设置渲染列整体的视觉样式和交互。不要在 containerProps 上设置会改变布局的样式。
  • shadcn 包装组件已在 DataTable 上渲染应用级表格框架(rounded-md border)。不要在每个表格实例上添加 rounded-md border;仅在有意覆盖框架时使用 className,例如设置 rounded-xl、border-0、border-2、阴影或表格变量。
  • shadcn 包装组件提供表格级 CSS 变量(--data-table-bg、--data-table-fg、--data-table-border、--data-table-muted、--data-table-muted-fg、--data-table-row-hover、--data-table-sticky-hover),并将它们用于粘性表头、粘性列、行和加载区域。适配宿主设计系统时,请在 DataTable 上或复制的包装组件默认值中一次性覆盖这些变量,而不要逐个设置粘性单元格的样式。
  • 通过 components 属性替换内部组件:Row、StickyColumnContainer、LoadingPlaceholder、LoadingOverlay、LoadingFooter(组件覆盖项必须转发 refs)。顶层组件:EmptyPlaceholder、ScrollElement。
  • context={{ ... }} 会传递给 computeRowKey、EmptyPlaceholder、加载插槽和组件覆盖项——但不会传递给单元格/表头渲染器(请在那里使用 React context)。请参阅 环境上下文。
  • 滚动模式:默认的内部滚动器、useWindowScroll 或 customScrollParent——只能选择一种。

从 TableVirtuoso 迁移

TableVirtuosodata-table
data 数组将 localModel({ data }) 作为 model 传入
itemContentDataTableColumn + DataTableCell
fixedHeaderContentDataTableColumnHeader
分组行groups + GroupHeaderCell
固定列(CSS)sticky="left" / sticky="right"
ref + scrollToIndexengineRef + scrollToRow$
rangeChangedonRenderedDataChange、viewportRange$、scrollLocation$

完整指南:从 TableVirtuoso 迁移。

故障排查

症状解决方法
表格为空白,或只显示表头为表格设置可测量的高度
表格未填满其面板使用可选的弹性高度模式:为父元素设置可测量的高度,祖先元素设置 min-h-0,界面框架元素设置 shrink-0,并将 DataTable 设置为 flex-1,且 height: '100%'
Shadcn 组件导入失败运行注册表安装命令,或从 @virtuoso.dev/data-table 导入无头组件
Shadcn 创建了一个名为 @/ 的字面目录确保 shadcn 读取的根配置中可以解析该别名;对于解决方案风格的 tsconfig,请在根 tsconfig.json 中镜像 @/* 路径
页面和表格都可以滚动只使用一种滚动模式
远程行始终不显示返回正确的获取结果格式(偏移模式下为 { rows, totalCount }),并传递 signal
排序后行重新挂载/状态丢失添加 computeRowKey
表体单元格与列重叠将宽度类从 DataTableCell 移至 DataTableColumnHeader;使用 DataTableColumn grow={...} 增加宽度
全宽表头样式裁切内容将布局样式保留在表头 className 中;containerProps 仅用于绘制和交互,不要设置内边距、边框、宽度、弹性布局或对齐方式
操作/显示列没有表头添加明确可见的 DataTableColumnHeader 标签,例如 Actions;field/id 用于标识,不是 UI 标签
外边框/框架重复移除调用处的 rounded-md border;shadcn 包装器已负责默认表格框架
粘性列/表头颜色与表体不一致在 DataTable 上覆盖 shadcn 包装器的 --data-table-* 变量,或在复制的包装器默认值中覆盖
粘性列被裁切检查父元素的 overflow 和表头的最小宽度
水平滚动时空单元格闪烁增大 columnOverscanCount

发布 shadcn 表格前,请搜索单元格上的宽度工具类,并将其移至表头:

rg 'DataTableCell.*className=.*(w-|min-w|max-w|basis-|grow|shrink|flex-(none|auto|initial|1|\[))'

对于测试,请用 VirtuosoDataTableTestingContext.Provider value={{ itemHeight, viewportHeight }} 包裹(JSDOM 没有布局),并断言行为,而不是精确的 DOM 行数——过扫描会渲染额外的行。粘性列、调整大小和拖拽交互请使用真实浏览器测试。参见测试。

参考资料

完整 API 参考:https://virtuoso.dev/data-table/

评论

支持 Markdown