Carbon React 自定义 DataTable 状态管理器:基于 Vite 示例的启动、运行与源码拆解
【免费下载链接】carbonA design system built by IBM项目地址: https://gitcode.com/GitHub_Trending/carbo/carbon
本文以 Carbon 仓库中 custom-data-table-state-manager-vite 示例为对象,讲解如何从零启动并运行一个"完全自定义状态管理"的 Carbon React 数据表格:包括仓库构建流程、Vite 开发服务器启动方式,以及该示例背后如何用一组自定义 Hooks 接管筛选、排序、分页与行选择状态。读完本文,你将掌握在 IBM Carbon React 中绕过内置<DataTable>状态机、自建数据表状态管理器的完整落地路径。
示例是什么:为什么需要自定义状态管理器
在@carbon/react中,数据表由两层组成:<DataTable>组件负责管理表格状态(排序、筛选、分页、选择),而<Table>、<TableRow>、<TableCell>、<TableHeader>等一系列组件只负责渲染 Carbon 设计语言的表格 UI。
这一分层意味着:当内置<DataTable>的状态管理无法满足业务场景时——例如需要懒加载当前页之外的表格行数据、与服务端状态源同步、或实现完全自定义的交互——应用完全可以放弃<DataTable>,仅使用 Carbon 的展示型表格组件,配合自己的状态管理器。这正是本示例存在的意义,其源码注释也明确写到:"THIS COMPONENT IS FOR DEMONSTRATION PURPOSES ONLY"(CustomDataTable.jsx)。
示例目录的核心文件结构如下:
src/components/CustomDataTable.jsx:自定义状态管理表格的主体组件,替代内置<DataTable>;src/components/Pagination.jsx:对 Carbon<Pagination>的包装,将"页码"语义转换为"起始行索引";src/hooks/:useFilteredRows、useSortInfo、useSortedRows、usePageInfo、useRowSelection、useCollator、useUniqueId七个状态管理 Hooks;src/misc/enums.js:表格尺寸、排序方向、排序周期等枚举常量;src/misc/doesRowMatchSearchString.js:行与搜索串的匹配工具;src/table-data.js:演示用的列定义、基础行数据与 50 行扩充数据;src/ExampleCustomDataTableApp.jsx:组装所有演示数据的入口应用组件。
启动与运行:从仓库构建到浏览器预览
第一步:在 carbon 仓库根目录完成构建
示例依赖仓库内构建产物(@carbon/react等包),因此 README 要求先在 carbon 仓库根目录执行一次安装与构建:
yarn install && yarn build该命令会安装仓库依赖,并通过 Lerna/Nx 等工具构建各子包(包括packages/react下的@carbon/react),确保后续示例引用的是本地最新构建的组件代码。
第二步:安装示例自身的依赖
构建完成后进入示例目录,安装其独立依赖:
cd packages/react/examples/custom-data-table-state-manager-vite yarn install # 或 npm install从示例的 package.json 可以看到其技术栈:
- 运行时依赖:
@carbon/react、react/react-dom(^18.2.0)、prop-types、use-debounce(用于搜索防抖)、carbon-icons; - 开发依赖:
vite(^7.x)、@vitejs/plugin-react、sass、eslint及 React 相关插件。
其中use-debounce是搜索功能防抖的关键依赖,直接服务于useFilteredRowsHook。
第三步:启动开发服务器
yarn dev # 或 npm run devdev脚本对应 package.json 中的"dev": "vite",即直接调用 Vite 开发服务器。Vite 配置非常精简,vite.config.js 仅注册了 React 插件:
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], })第四步:浏览器预览
用浏览器打开 http://localhost:5173/ 即可看到示例效果。入口由 index.html 指向/src/main.jsx,后者通过ReactDOM.createRoot(document.getElementById('root'))挂载<App />(main.jsx),App再渲染ExampleCustomDataTableApp,最终以pageSize={5}、start={0}、hasSelection={true}的配置实例化CustomDataTable(ExampleCustomDataTableApp.jsx)。
此外package.json还提供了npm run preview(Vite 构建产物预览)与npm run build(vite build生产构建)、npm run lint(ESLint 检查,--max-warnings 0零容忍警告)等脚本,方便验证生产包与代码质量。
核心 API:CustomDataTable 的 props 契约
CustomDataTable是示例对外暴露的唯一组件,其 props 全部带 PropTypes 校验(CustomDataTable.jsx):
| prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
columns | Array<{id, title, sortCycle}> | — | 列定义;sortCycle指定该列的排序周期 |
rows | Array<{id, selected, ...}> | — | 表格数据行,id为数字唯一键 |
sortInfo | {columnId, direction} | — | 初始排序信息,方向取NONE/ASC/DESC |
hasSelection | boolean | false | 是否渲染行选择(多选)UI |
pageSize | number | 5 | 每页行数 |
start | number | 0 | 当前页起始行索引(从 0 开始) |
size | string | 'lg' | 表格尺寸,见TABLE_SIZE |
collator | Intl.Collator | new Intl.Collator() | 用于排序的国际化排序器 |
zebra | boolean | — | 是否显示斑马纹 |
id | string | — | 用于生成选择框唯一 id 前缀 |
其中sortInfo.direction、size的可选值均来自 enums.js:
TABLE_SIZE:SHORT: 'short'、REGULAR: 'lg'、TALL: 'tall';TABLE_SORT_DIRECTION:NONE、ASCENDING: 'ASC'、DESCENDING: 'DESC';TABLE_SORT_CYCLE:bi-states-from-ascending、bi-states-from-descending、tri-states-from-ascending、tri-states-from-descending,分别表示"从升序开始的双态/三态"与"从降序开始的双态/三态"四种循环模式,映射关系集中在TABLE_SORT_CYCLES(enums.js)。
状态管理拆解:七个 Hooks 如何各司其职
CustomDataTable的巧妙之处在于把所有表格状态拆解为可复用的 Hooks,在组件内按固定顺序组合(CustomDataTable.jsx):
const [rows, setRows] = useState(propRows); // 原始行数据(含选中态) const [sortInfo, setSortInfo] = useSortInfo(propSortInfo); // 排序状态 const [filteredRows, searchString, setSearchString] = useFilteredRows(rows); // 搜索筛选 const [setRowSelection] = useRowSelection( filteredRows, searchString, setRows); // 行选择 const [sortedRows] = useSortedRows(filteredRows, sortInfo, collator); // 排序 const [start, pageSize, setStart, setPageSize] = usePageInfo( propStart, propPageSize, filteredRows.length); // 分页useFilteredRows:防抖搜索筛选
useFilteredRows.js 借助use-debounce的useDebounce(searchString, 500)实现500ms 防抖:用户在工具栏搜索框输入时,不会立即触发昂贵的过滤计算,而是在停止输入 500ms 后基于debouncedSearchString用useMemo重新计算filteredRows。行匹配规则由doesRowMatchSearchString定义(doesRowMatchSearchString.js):遍历行的每个字段(排除id),只要任一字段的字符串包含搜索串即视为命中:
const doesRowMatchSearchString = (row, searchString) => Object.keys(row).some( (key) => key !== 'id' && String(row[key] ?? '').indexOf(searchString) >= 0 );useSortInfo:排序状态机与周期切换
useSortInfo.js 维护当前排序列与方向。核心是getNextSort():根据列的sortCycle从TABLE_SORT_CYCLES取方向数组,oldDirection找到当前下标后(index + 1) % length推进到下一状态。例如tri-states-from-ascending的循环是NONE → ASC → DESC → NONE,而bi-states-from-ascending是ASC ↔ DESC。一个值得注意的细节:当非主排序列(columnId !== 'name')进入NONE状态时,会重置回initialSortInfo,避免表格完全失去排序基准。
useSortedRows:结合 Intl.Collator 的稳定排序
useSortedRows.js 通过useCollator封装Intl.Collator(默认new Intl.Collator()),对多语言文本排序更友好。排序方向映射为系数ASC → 1、DESC → -1,方向为NONE时直接返回原数组;排序时先用slice()复制再sort(),避免原地修改 React state。
usePageInfo:起始行索引与越界修正
usePageInfo.js 用start(从 0 开始的起始行索引)+pageSize替代"当前页码"。组件渲染时通过sortedRows.slice(start, start + pageSize)截取当前页。它还处理了一个边界场景:当筛选/删除导致行数骤减、start越过数据末尾时,会自动把start回退到合法的最后一页起点(下限为 0)。
useRowSelection:单选、全选与"选中过滤后所有行"
useRowSelection.js 接受rowId与selected两个参数:
rowId为具体行 id 时只更新该行;rowId为undefined时表示全选/全不选,此时若存在搜索串,只会把匹配搜索串的行(doesRowMatchSearchString命中)全部置为选中/取消,实现了"对当前过滤结果批量选择"。
useUniqueId:选择框唯一 id
useUniqueId为表格生成稳定且唯一的 id 前缀,配合useCollator(国际化排序器)共同服务于组件内部对选择框name/id的组装。
渲染层:如何拼装 Carbon 表格 UI
CustomDataTable的渲染完全使用@carbon/react的展示型组件(CustomDataTable.jsx),结构如下:
<TableContainer>:包裹容器,提供标题title="DataTable"与描述description="Fully customized";<TableToolbar>:工具栏,内部含<TableBatchActions>(批量操作区,shouldShowBatchActions控制显隐,totalSelected展示已选数量,内含一个Delete批量删除按钮)与<TableToolbarContent>(<TableToolbarSearch>搜索框 +<TableToolbarMenu>下拉菜单,含三个alert演示动作)。工具项在批量操作显示时通过tabIndex={-1}移出 Tab 焦点,保证键盘可达性;<Table size={size} isSortable>:可排序表格主体。表头<TableHeader>通过data-column-id、data-sort-cycle、data-sort-direction三个 data 属性暴露排序所需信息,点击时触发handleChangeSort调用setSortInfo;isSortHeader与sortDirection驱动 Carbon 自带的排序箭头 UI;<TableBody zebra={zebra}>:对sortedRows.slice(start, start + pageSize)逐行渲染<TableRow>与<TableCell>;每行用data-row-id标记行 id,配合handleChangeSelection从event.currentTarget.closest('tr')反查行 id 完成单选;hasSelection为真时额外渲染<TableSelectAll>/<TableSelectRow>;<Pagination>:分页条。与内置DataTable相比,示例的包装组件(Pagination.jsx)将 Carbon 的page(从 1 开始)换算为零基start:page = Math.floor(start / pageSize) + 1,翻页时通过Math.min(Math.max(...), count)把新起始索引钳制在合法范围内,再分别回调onChangePageSize与onChangeStart。
值得注意的是,示例在表头交互中大量使用data 属性 + 事件委托的方式(而非向每个表头传入闭包),这让表头组件保持纯展示性,状态更新统一收敛到CustomDataTable的回调里,是整个自定义状态管理器的关键设计。
演示数据与可运行验证
示例数据定义在 table-data.js:columns包含name(双态排序)、protocol、port(三态排序)、rule、attachedGroups、status六列;rows为 3 条负载均衡器示例数据(其中一条默认selected: true);rowsMany通过 50 次映射把 3 条模板行扩成 50 行,并将name格式化为Load Balancer 001风格;sortInfo初始化为按name升序。ExampleCustomDataTableApp最终以 50 行数据、每页 5 行、支持选择、默认按名称排序的配置渲染,你可以直接在浏览器中验证搜索防抖、三态排序、批量删除、翻页与每页行数切换等全部交互。
如果你需要将本示例移植到自己的应用,只需保留src/hooks/、src/misc/与两个组件文件,替换table-data.js中的业务数据与列定义,即可获得一套与 Carbon 视觉规范完全一致、但状态完全自控的数据表方案——这正是该示例被定位为"应用级状态管理器起点"的原因。
【免费下载链接】carbonA design system built by IBM项目地址: https://gitcode.com/GitHub_Trending/carbo/carbon
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考