ProComponents, templates & AI tooling
HeroUI
27.7k

Table

Table 从 HeroUI v2 到 v3 的迁移指南。

完整的 API 参考、样式指南与高级示例请参阅 v3 Table 文档。本指南只关注从 HeroUI v2 的迁移。

结构变化

在 v2 中,Table 对每个部分使用独立的命名导入:

import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell } from "@heroui/react";

export default function App() {
  return (
    <Table aria-label="Example table">
      <TableHeader>
        <TableColumn>Name</TableColumn>
        <TableColumn>Role</TableColumn>
      </TableHeader>
      <TableBody>
        <TableRow key="1">
          <TableCell>Kate Moore</TableCell>
          <TableCell>CEO</TableCell>
        </TableRow>
      </TableBody>
    </Table>
  );
}

在 v3 中,Table 使用点语法的复合组件,并新增 Table.ScrollContainerTable.Content

import { Table } from "@heroui/react";

export default function App() {
  return (
    <Table>
      <Table.ScrollContainer>
        <Table.Content aria-label="Example table">
          <Table.Header>
            <Table.Column>Name</Table.Column>
            <Table.Column>Role</Table.Column>
          </Table.Header>
          <Table.Body>
            <Table.Row>
              <Table.Cell>Kate Moore</Table.Cell>
              <Table.Cell>CEO</Table.Cell>
            </Table.Row>
          </Table.Body>
        </Table.Content>
      </Table.ScrollContainer>
    </Table>
  );
}

主要变化

1. 组件结构

v2: 独立的命名导入(TableTableHeaderTableColumnTableBodyTableRowTableCell
v3: 单一 Table 导入配合点语法:TableTable.ScrollContainerTable.ContentTable.HeaderTable.ColumnTable.BodyTable.RowTable.CellTable.Footer

2. 新增包裹层

  • Table:根容器(样式外层)
  • Table.ScrollContainer:横向滚动与自定义滚动条
  • Table.Content:实际的 <table> 元素(aria-labelselectionModesortDescriptor 等放在这里)
  • Table.Footer:替代 bottomContent,用于分页等底部内容

3. Prop 变更

v2 propv3 对应位置说明
aria-labelTable.Contentaria-label移到 Table.Content
selectionModeTable.Content移到 Table.Content
selectedKeysTable.Content移到 Table.Content
defaultSelectedKeysTable.Content移到 Table.Content
onSelectionChangeTable.Content移到 Table.Content
sortDescriptorTable.Content移到 Table.Content
onSortChangeTable.Content移到 Table.Content
disabledKeysTable.Content移到 Table.Content
disallowEmptySelectionTable.Content移到 Table.Content
selectionBehaviorTable.Content移到 Table.Content
disabledBehaviorTable.Content移到 Table.Content
onRowActionTable.Content移到 Table.Content
onCellActionTable.Content移到 Table.Content
topContent放在 Table 内、Table.ScrollContainer 之前
bottomContent使用 Table.Footer
topContentPlacement已移除(直接组合布局)
bottomContentPlacement已移除(直接组合布局)
color已移除(请用 Tailwind CSS)
variantTablevariant变为 "primary"(卡片式,默认)或 "secondary"(扁平)
layout已移除
radius已移除(请用 Tailwind CSS)
shadow已移除(请用 Tailwind CSS)
isStriped已移除(请用 Tailwind CSS)
isCompact已移除(请用 Tailwind CSS)
isHeaderSticky已移除(请用 Tailwind CSS,例如 sticky top-0
fullWidth已移除(默认全宽)
removeWrapper已移除(直接组合布局)
hideHeader已移除(省略 Table.Header 或用 CSS)
isVirtualized使用 React Aria 的 Virtualizer 包裹
maxTableHeight使用 CSS 或 Virtualizer
rowHeight与 Virtualizer 一起使用 TableLayout
isKeyboardNavigationDisabled已移除
disableAnimation已移除
classNames在各复合子组件上使用 className

4. 使用 Checkbox 的选择

v2: 设置 selectionMode 后由表格自动渲染 Checkbox。
v3: 在列与行中显式使用带 slot="selection"Checkbox

5. 加载与空状态

v2: TableBody 上的 loadingStateloadingContentemptyContent
v3: Table.Body 上的 renderEmptyState;无限滚动加载用 Table.LoadMore

6. 分页

v2: Table 上的 bottomContent
v3: Table.Footer 复合组件。

7. 列宽调整

v2: 非内置能力。
v3: Table.ResizableContainer + Table.ColumnResizer 复合组件。

迁移示例

基础表格

import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell } from "@heroui/react";

<Table aria-label="Users">
  <TableHeader>
    <TableColumn>Name</TableColumn>
    <TableColumn>Role</TableColumn>
    <TableColumn>Status</TableColumn>
  </TableHeader>
  <TableBody>
    <TableRow key="1">
      <TableCell>Kate Moore</TableCell>
      <TableCell>CEO</TableCell>
      <TableCell>Active</TableCell>
    </TableRow>
    <TableRow key="2">
      <TableCell>John Doe</TableCell>
      <TableCell>Developer</TableCell>
      <TableCell>Active</TableCell>
    </TableRow>
  </TableBody>
</Table>
import { Table } from "@heroui/react";

<Table>
  <Table.ScrollContainer>
    <Table.Content aria-label="Users">
      <Table.Header>
        <Table.Column>Name</Table.Column>
        <Table.Column>Role</Table.Column>
        <Table.Column>Status</Table.Column>
      </Table.Header>
      <Table.Body>
        <Table.Row id="1">
          <Table.Cell>Kate Moore</Table.Cell>
          <Table.Cell>CEO</Table.Cell>
          <Table.Cell>Active</Table.Cell>
        </Table.Row>
        <Table.Row id="2">
          <Table.Cell>John Doe</Table.Cell>
          <Table.Cell>Developer</Table.Cell>
          <Table.Cell>Active</Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table.Content>
  </Table.ScrollContainer>
</Table>

动态行

const columns = [
  { key: "name", label: "Name" },
  { key: "role", label: "Role" },
];
const rows = [
  { key: "1", name: "Kate", role: "CEO" },
  { key: "2", name: "John", role: "Developer" },
];

<Table aria-label="Users">
  <TableHeader columns={columns}>
    {(column) => <TableColumn key={column.key}>{column.label}</TableColumn>}
  </TableHeader>
  <TableBody items={rows}>
    {(item) => (
      <TableRow key={item.key}>
        {(columnKey) => <TableCell>{item[columnKey]}</TableCell>}
      </TableRow>
    )}
  </TableBody>
</Table>
const columns = [
  { id: "name", label: "Name" },
  { id: "role", label: "Role" },
];
const rows = [
  { id: "1", name: "Kate", role: "CEO" },
  { id: "2", name: "John", role: "Developer" },
];

<Table>
  <Table.ScrollContainer>
    <Table.Content aria-label="Users">
      <Table.Header columns={columns}>
        {(column) => <Table.Column id={column.id}>{column.label}</Table.Column>}
      </Table.Header>
      <Table.Body items={rows}>
        {(item) => (
          <Table.Row id={item.id}>
            <Table.Cell>{item.name}</Table.Cell>
            <Table.Cell>{item.role}</Table.Cell>
          </Table.Row>
        )}
      </Table.Body>
    </Table.Content>
  </Table.ScrollContainer>
</Table>

选择

const [selectedKeys, setSelectedKeys] = useState(new Set(["1"]));

<Table
  aria-label="Users"
  selectionMode="multiple"
  selectedKeys={selectedKeys}
  onSelectionChange={setSelectedKeys}
>
  <TableHeader>
    <TableColumn>Name</TableColumn>
    <TableColumn>Role</TableColumn>
  </TableHeader>
  <TableBody>
    <TableRow key="1">
      <TableCell>Kate</TableCell>
      <TableCell>CEO</TableCell>
    </TableRow>
    <TableRow key="2">
      <TableCell>John</TableCell>
      <TableCell>Developer</TableCell>
    </TableRow>
  </TableBody>
</Table>
import { Table, Checkbox } from "@heroui/react";

const [selectedKeys, setSelectedKeys] = useState(new Set(["1"]));

<Table>
  <Table.ScrollContainer>
    <Table.Content
      aria-label="Users"
      selectionMode="multiple"
      selectedKeys={selectedKeys}
      onSelectionChange={setSelectedKeys}
    >
      <Table.Header>
        <Table.Column>
          <Checkbox slot="selection" />
        </Table.Column>
        <Table.Column>Name</Table.Column>
        <Table.Column>Role</Table.Column>
      </Table.Header>
      <Table.Body>
        <Table.Row id="1">
          <Table.Cell>
            <Checkbox slot="selection" />
          </Table.Cell>
          <Table.Cell>Kate</Table.Cell>
          <Table.Cell>CEO</Table.Cell>
        </Table.Row>
        <Table.Row id="2">
          <Table.Cell>
            <Checkbox slot="selection" />
          </Table.Cell>
          <Table.Cell>John</Table.Cell>
          <Table.Cell>Developer</Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table.Content>
  </Table.ScrollContainer>
</Table>

排序

const [sortDescriptor, setSortDescriptor] = useState({
  column: "name",
  direction: "ascending",
});

<Table
  aria-label="Users"
  sortDescriptor={sortDescriptor}
  onSortChange={setSortDescriptor}
>
  <TableHeader>
    <TableColumn key="name" allowsSorting>Name</TableColumn>
    <TableColumn key="role" allowsSorting>Role</TableColumn>
  </TableHeader>
  <TableBody items={sortedItems}>
    {(item) => (
      <TableRow key={item.key}>
        {(columnKey) => <TableCell>{item[columnKey]}</TableCell>}
      </TableRow>
    )}
  </TableBody>
</Table>
const [sortDescriptor, setSortDescriptor] = useState({
  column: "name",
  direction: "ascending",
});

<Table>
  <Table.ScrollContainer>
    <Table.Content
      aria-label="Users"
      sortDescriptor={sortDescriptor}
      onSortChange={setSortDescriptor}
    >
      <Table.Header>
        <Table.Column id="name" allowsSorting>Name</Table.Column>
        <Table.Column id="role" allowsSorting>Role</Table.Column>
      </Table.Header>
      <Table.Body items={sortedItems}>
        {(item) => (
          <Table.Row id={item.id}>
            <Table.Cell>{item.name}</Table.Cell>
            <Table.Cell>{item.role}</Table.Cell>
          </Table.Row>
        )}
      </Table.Body>
    </Table.Content>
  </Table.ScrollContainer>
</Table>

分页(底部内容)

<Table
  aria-label="Users"
  bottomContent={
    <Pagination total={10} page={page} onChange={setPage} />
  }
  bottomContentPlacement="outside"
>
  {/* ... */}
</Table>
<Table>
  <Table.ScrollContainer>
    <Table.Content aria-label="Users">
      {/* Header and Body */}
    </Table.Content>
  </Table.ScrollContainer>
  <Table.Footer>
    {/* Pagination component */}
  </Table.Footer>
</Table>

空状态

<TableBody emptyContent="No rows to display.">
  {[]}
</TableBody>
<Table.Body
  items={[]}
  renderEmptyState={() => (
    <p className="text-center py-4">No rows to display.</p>
  )}
>
  {[]}
</Table.Body>

样式相关变化

v2:classNames prop

<Table
  classNames={{
    base: "custom-base",
    wrapper: "custom-wrapper",
    table: "custom-table",
    thead: "custom-header",
    tbody: "custom-body",
    tr: "custom-row",
    th: "custom-column",
    td: "custom-cell",
  }}
/>

v3:直接使用 className

<Table className="custom-base">
  <Table.ScrollContainer className="custom-wrapper">
    <Table.Content aria-label="Table" className="custom-table">
      <Table.Header className="custom-header">
        <Table.Column className="custom-column">Name</Table.Column>
      </Table.Header>
      <Table.Body className="custom-body">
        <Table.Row className="custom-row">
          <Table.Cell className="custom-cell">Value</Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table.Content>
  </Table.ScrollContainer>
</Table>

组件结构(Anatomy)

v3 的 Table 结构如下:

Table (Root container)
  ├── Table.ScrollContainer (horizontal scroll)
  │   └── Table.Content (<table> element, aria-label, selectionMode, etc.)
  │       ├── Table.Header (<thead>)
  │       │   └── Table.Column (<th>, allowsSorting, etc.)
  │       │       └── Table.ColumnResizer (optional)
  │       └── Table.Body (<tbody>, items, renderEmptyState)
  │           ├── Table.Row (<tr>)
  │           │   └── Table.Cell (<td>)
  │           └── Table.LoadMore (optional, infinite scroll)
  │               └── Table.LoadMoreContent
  └── Table.Footer (optional, pagination, etc.)

数据项标识

v2: React 的 key 同时用于列表调和与选择状态。
v3:Table.RowTable.Column 上使用 id 承载选择/排序状态;列表渲染仍保留 React 的 key

总结

  1. 导入方式:由多个命名导入 → 单一 Table 导入配合点语法。
  2. 新增包裹层Table.ScrollContainerTable.Content 包裹表格结构。
  3. prop 迁移aria-labelselectionModesortDescriptor 等从 Table 移到 Table.Content
  4. 底部区域bottomContentTable.Footer
  5. 顶部区域topContent → 放在 Table 内、Table.ScrollContainer 之前。
  6. 选择用 Checkbox:由自动渲染 → 显式使用 slot="selection"Checkbox
  7. 空状态emptyContentTable.BodyrenderEmptyState
  8. 加载loadingState / loadingContent → 无限滚动场景用 Table.LoadMore
  9. 列宽调整:新增 Table.ResizableContainerTable.ColumnResizer
  10. 标识字段:行/列由 key 表达业务标识 → 使用 id
  11. 样式类 prop 移除colorradiusshadowisStripedisCompact 等 → 请用 Tailwind CSS。
  12. classNames 移除:在各复合子组件上使用 className

本页目录