TanStack Octane Table 组合式表格指南:用 createTableHook 构建可复用的应用级表格工厂
前端UI组件【免费下载链接】table Headless UI for building powerful tables datagrids for TS/JS - React-Table, Vue-Table, Solid-Table, Svelte-Table项目地址https://gitcode.com/gh_mirrors/ta/table点击查看免费下载createTableHook是 TanStack Octane Table 提供的一种表格工厂式 API它允许你在应用内定义一次共享的特性集features、行模型row models与默认表格选项随后用useAppTable为每个业务表格注入各自独有的列定义columns与数据data。这套模式与 TanStack Form 的createFormHook一一对应既适合追求轻量的共享配置用法也支持进阶的组件注册表用法注册可复用的表格、单元格与表头组件。读完本文你将掌握createTableHook的完整调用方式、默认配置与单表覆盖机制、App 系列包装组件的渲染原理以及如何在多个表格之间复用统一的基础设施与 UI 约定。背景为什么需要应用级表格工厂在大型应用中多个业务页面往往拥有各自的表格但它们在能力上高度相似都需要排序、过滤、分页、行选择都使用相同的工具栏、单元格渲染器和表头组件。如果每个表格都独立调用底层的useTable并各自拼装features、行模型和默认选项就会出现大量重复样板代码且当基础设施调整例如新增一个 filterFn时需要逐个表格同步修改。createTableHook正是为消除这种重复而设计它把一次定义、处处复用的思想落到了表格 API 上。源码注释中明确写道这是the table equivalent of TanStack FormscreateFormHook见 createTableHook.tsrx通过一次调用同时获得useAppTable创建带共享特性、行模型、默认选项与已注册组件的表格createAppColumnHelper预绑定TFeatures与已注册组件类型的列辅助器useTableContext/useCellContext/useHeaderContext在已注册组件内部读取当前表格、单元格与表头实例。组件注册是可选的。官方推荐路线是先共享特性与选项再按需添加可复用组件见 composable-tables.md。第一步共享特性与默认选项首先创建一个应用级 hook把特性集、行模型和共享默认值集中放置。以下示例让所有由useAppTable创建的表格默认具备排序能力import { createSortedRowModel, createTableHook, rowSortingFeature, sortFns, tableFeatures, } from tanstack/octane-table const features tableFeatures({ rowSortingFeature, sortedRowModel: createSortedRowModel(), sortFns, }) const { useAppTable, createAppColumnHelper } createTableHook({ features, debugTable: true, enableSortingRemoval: false, })关键点传给createTableHook的选项会成为useAppTable创建的所有表格的默认值features选项同时被绑定到返回的列辅助器上因此列定义可以感知排序等 API 的存在无需在每个表格里重复声明typeof features。从实现上看createTableHook接收...defaultTableOptions剩余参数并在useAppTable内部以{ ...defaultTableOptions, ...tableOptions }的方式合并——调用点传入的选项优先见 createTableHook.tsrx这是默认值 单表覆盖机制的底层依据。返回结果中还包含appFeatures字段即传入的features方便需要显式引用特性类型的场景见 createTableHook.tsrx。第二步创建应用级列定义每个行类型row type创建一个列辅助器。辅助器已经绑定了应用的特性集列定义中不再需要手动贯穿typeof featurestype Person { firstName: string lastName: string age: number visits: number } const columnHelper createAppColumnHelperPerson() const columns columnHelper.columns([ columnHelper.accessor(firstName, { cell: (info) info.getValue(), }), columnHelper.accessor((row) row.lastName, { id: lastName, header: () spanLast Name/span, cell: (info) i{info.getValue()}/i, }), columnHelper.accessor(age, { header: Age, }), columnHelper.accessor(visits, { header: Visits, }), ])在运行时层面createAppColumnHelper与核心库的createColumnHelper是同一个实现——源码通过coreCreateColumnHelperTFeatures, TData()直接返回并通过类型断言叠加增强的列定义类型AppColumnHelper见 createTableHook.tsrx。也就是说组件是在渲染阶段通过上下文附加的类型系统则保证你在编写列定义时就能获得cell.TextCell之类的提示。第三步创建表格并渲染每个表格通过useAppTable创建。调用点只提供表格特有的输入如columns与data共享的特性与默认值来自 hookfunction UsersTable({ data }: { data: Person[] }) { const table useAppTable( { columns, data, }, (state) ({ sorting: state.sorting }), ) // render with the table instance }第二个参数是可选的状态选择器selector用于订阅表格状态切片其实现对应useTable的选择器机制。渲染时可以完全沿用独立useTable表格的实例 API。这条简单路径不需要AppTable、AppCell、AppHeader或任何已注册组件table thead {table.getHeaderGroups().map((headerGroup) ( tr key{headerGroup.id} {headerGroup.headers.map((header) ( th key{header.id} onClick{header.column.getToggleSortingHandler()} {header.isPlaceholder ? null : table.FlexRender header{header} /} /th ))} /tr ))} /thead tbody {table.getRowModel().rows.map((row) ( tr key{row.id} {row.getAllCells().map((cell) ( td key{cell.id} table.FlexRender cell{cell} / /td ))} /tr ))} /tbody /table第四步按表格覆盖共享默认值传给useAppTable的选项会覆盖createTableHook的默认值。少数需要不同行为的表格无需另建 hook直接覆盖即可const table useAppTable( { columns, data, enableSortingRemoval: true, }, (state) ({ sorting: state.sorting }), )进阶把 createTableHook 用作组件注册表当多个表格需要共享相同的工具栏控件、单元格渲染器、表头/表尾渲染器时可以利用createTableHook的组件注册能力。仓库中的 composable-tables 示例 把共享配置集中在src/hooks/table.ts同时创建了 Users 与 Products 两个表格二者复用同一套 hook 与组件仅列定义和数据不同见 main.tsrx。组件注册表搭建以下是示例中src/hooks/table.ts的完整形态与 table.ts 保持一致import { columnFilteringFeature, createFilteredRowModel, createPaginatedRowModel, createSortedRowModel, createTableHook, filterFns, rowPaginationFeature, rowSortingFeature, sortFns, tableFeatures, } from tanstack/octane-table import { PaginationControls, RowCount, TableToolbar, } from ../components/table-components import { CategoryCell, NumberCell, PriceCell, ProgressCell, RowActionsCell, StatusCell, TextCell, } from ../components/cell-components import { ColumnFilter, FooterColumnId, FooterSum, SortIndicator, } from ../components/header-components const features tableFeatures({ columnFilteringFeature, rowPaginationFeature, rowSortingFeature, sortedRowModel: createSortedRowModel(), filteredRowModel: createFilteredRowModel(), paginatedRowModel: createPaginatedRowModel(), sortFns, filterFns, }) export const { createAppColumnHelper, useAppTable, useTableContext, useCellContext, useHeaderContext, } createTableHook({ features, getRowId: (row) row.id, tableComponents: { PaginationControls, RowCount, TableToolbar, }, cellComponents: { TextCell, NumberCell, StatusCell, ProgressCell, RowActionsCell, PriceCell, CategoryCell, }, headerComponents: { SortIndicator, ColumnFilter, FooterColumnId, FooterSum, }, })实际示例的features中还额外配置了行选择特性rowSelectionFeature以及自定义的排序与过滤函数映射sortFns传入sortFn_alphanumeric、sortFn_textfilterFns传入filterFn_includesString、filterFn_inNumberRange见 table.ts说明features块完全支持按需裁剪。返回的辅助 APIHelper用途useAppTable以共享特性、行模型、默认选项与已注册组件创建表格。createAppColumnHelper创建已绑定TFeatures与注册组件类型的列辅助器。useTableContext在已注册的表格组件中读取当前表格实例。useCellContext在已注册的单元格组件中读取当前单元格实例。useHeaderContext在已注册的表头/表尾组件中读取当前表头实例。[!IMPORTANT]useTableContext、useCellContext、useHeaderContext必须从调用createTableHook的同一模块导入如上所示。这些 hook 携带你的TFeatures与注册组件映射的类型因此table.PaginationControls、cell.TextCell、header.SortIndicator都是类型安全的。细节与作用域上下文scoped context的逃生通道见 Table Context 指南。组件列定义每个行类型创建一个列辅助器列定义可以直接引用已注册组件const personColumnHelper createAppColumnHelperPerson() const columns useMemo( () personColumnHelper.columns([ personColumnHelper.accessor(firstName, { header: First Name, footer: (props) props.column.id, cell: ({ cell }) cell.TextCell /, }), personColumnHelper.accessor(age, { header: Age, footer: (props) props.column.id, cell: ({ cell }) cell.NumberCell /, }), personColumnHelper.display({ id: actions, header: Actions, cell: ({ cell }) cell.RowActionsCell /, }), ]), [], )已注册的单元格组件内部使用useCellContext()已注册的表头/表尾组件使用useHeaderContext()。例如NumberCell通过useCellContextnumber()拿到单元格并调用cell.getValue().toLocaleString()做本地化格式化StatusCell则按枚举值渲染状态徽章SelectCell通过cell.row读取当前行的选择状态并渲染复选框见 cell-components.tsrx。App 包装组件渲染表格每个表格仍用useAppTable创建传入表格特有选项columns、data等共享的features含行模型工厂、getRowId与组件注册表来自 hookconst table useAppTable( { columns, data, debugTable: true, }, (state) state, )返回的表格对象上挂载了AppTable、AppHeader、AppCell、AppFooter四个包装组件。示例使用AppTable配合选择器让渲染过程只订阅该表格实际用到的状态切片table.AppTable selector{(state) ({ pagination: state.pagination, sorting: state.sorting, columnFilters: state.columnFilters, })} {({ sorting, columnFilters }) ( div classNametable-container table.TableToolbar titleUsers Table onRefresh{refreshData} / table thead {table.getHeaderGroups().map((headerGroup) ( tr key{headerGroup.id} {headerGroup.headers.map((h) ( table.AppHeader header{h} key{h.id} {(header) ( th onClick{header.column.getToggleSortingHandler()} header.FlexRender / header.SortIndicator / header.ColumnFilter / /th )} /table.AppHeader ))} /tr ))} /thead tbody {table.getRowModel().rows.map((row) ( tr key{row.id} {row.getAllCells().map((c) ( table.AppCell cell{c} key{c.id} {(cell) ( td cell.FlexRender / /td )} /table.AppCell ))} /tr ))} /tbody /table table.PaginationControls / table.RowCount / /div )} /table.AppTable这组 App 包装组件的底层实现值得留意见 createTableHook.tsrx稳定的组件引用useTable每次渲染都会返回新的table引用因此包装组件不能闭包该引用否则每次状态更新都会重建组件子树例如受控输入会在每次按键时失去焦点。实现通过tableRef每次渲染刷新配合useMemo保持组件实例稳定上下文注入AppTable通过TableContext.Provider提供当前表格AppCell、AppHeader通过各自的 Provider 提供扩展后的单元格/表头实例预绑定扩展AppCell会把FlexRender基于上下文的CellFlexRender与全部cellComponents通过Object.assign附加到同一单元格实例上见 createTableHook.tsrxAppHeader/AppFooter对表头实例做同样的处理可选订阅三个包装组件都支持selector属性传入时内部包一层TableSubscribe实现按状态切片订阅最终合并extendedTable把AppTable、AppCell、AppHeader、AppFooter与全部tableComponents合并到table对象上见 createTableHook.tsrx这就是table.AppTable、table.TableToolbar可直接使用的来源。AppFooter与AppHeader共用Header类型区别仅在于其上下文绑定的FlexRender渲染的是columnDef.footer见 createTableHook.tsrx所以表尾同样可以使用headerComponents中注册的FooterSum、FooterColumnId等组件。复用组件注册表示例从同一个createAppColumnHelper分别创建personColumnHelper与productColumnHelper再用同一个useAppTable工厂渲染 Users 与 Products 两个表格见 main.tsrx。每个表格拥有自己的数据与列定义而表格基础设施与组件约定归应用 hook 所有——这正是组合式表格的核心收益。完整示例中两个表格还各自实现了表尾tfoot数值列用FooterSum求和并显示过滤提示其余列用FooterColumnId展示列 ID用户可对照 main.tsrx 的 Users 与 Products 两个实现阅读。作用域上下文Scoped Contexts默认情况下createTableHook会把AppTable/AppCell/AppHeader的 Provider 连接到共享的模块级上下文返回的 hook 也从同一上下文读取因此多数场景下你不需要关心上下文接线。由于上下文位于模块作用域其身份在本地开发的热更新HMR期间保持稳定编辑组件不会导致 Provider 与消费者指向不匹配的上下文。只有在嵌套不同表格设置、且消费者可能读取到最近的内层Provider 时才需要隔离上下文用createTableHookContexts创建一套全新作用域的上下文再传入createTableHook// scoped-table-context.ts无组件导入 import { createTableHookContexts, tableFeatures } from tanstack/octane-table export const features tableFeatures({/* ... */}) export const { tableContext, cellContext, headerContext, useTableContext, useCellContext, useHeaderContext, } createTableHookContextstypeof features()// table.ts import { createTableHook } from tanstack/octane-table import { cellContext, features, headerContext, tableContext, } from ./scoped-table-context export const { useAppTable } createTableHook({ features, tableContext, // Provider 改用这套作用域上下文 cellContext, headerContext, tableComponents: {/* ... */}, })每次调用createTableHookContexts都会返回一组全新的上下文见 createTableHookContexts.ts因此两套使用各自作用域上下文的表格可以互相嵌套而不会读到对方的值。需要说明的是createTableHookContexts返回的use*Contexthook 只携带TFeatures类型、并不知道注册组件映射组件映射是在之后的createTableHook里定义的见 createTableHookContexts.ts。因此优先使用createTableHook返回的 hook类型最丰富tableComponents/cellComponents/headerComponents均已键入仅当需要从无法导入createTableHook结果的模块中读取上下文时才使用createTableHookContexts的 hook。这套设计与 TanStack Form 的createFormHookContexts一脉相承上下文 hook 刻意保持松散强类型路径位于表格/字段组件上见 table-context.md。组件内读取上下文的实用技巧根据 Table Context 指南多数情况下你甚至不需要useTableContext每个cell、header、row、column实例都已携带回其表格的引用cell.table、header.table、row.table、column.table实例之间也互相链接cell.row、cell.column、header.column、row.getAllCells()。因此单元格/表头组件可以直接从已有实例出发导航到所需对象只有像独立工具栏、分页控件这类没有起始实例的组件才需要useTableContext()。另外要注意表格实例的引用每次状态变化都会刷新它并不是稳定的上下文值而cell、row、column、header实例是稳定引用。若把状态相关的方法读取如cell.getValue()、cell.row.getIsSelected()放进记忆化组件应通过Subscribe或useSelector包裹确保在依赖的状态变化时重新执行详见 table-state.md 与 table-context.md。何时使用这种模式多个表格需要共享特性、行模型、默认选项或约定时使用createTableHookuseAppTable例如本文介绍的 composable-tables 示例中 Users 与 Products 两个表格共用一套基础设施只是创建一次性表格时使用独立的useTableAPI 即可不必引入工厂层需要标准化的可复用表格 UI 部件时再叠加组件注册表tableComponents/cellComponents/headerComponents为应用沉淀统一的表格 UI 约定。进一步阅读最小化 useAppTable 示例最简createTableHook搭建展示AppTable/AppCell/AppHeader/AppFooter与注册组件的最小用法Composable Tables 完整示例Users 与 Products 两个表格共享src/hooks/table.ts与可复用组件Table Context 指南上下文提供机制、类型安全读取与 scoped context 逃生通道Table State 指南Subscribe与 selector 模式核心实现createTableHook.tsrx 与 createTableHookContexts.ts。赞分享前端UI组件【免费下载链接】table Headless UI for building powerful tables datagrids for TS/JS - React-Table, Vue-Table, Solid-Table, Svelte-Table项目地址https://gitcode.com/gh_mirrors/ta/table点击查看免费下载相关推荐TanStack Lit Table 可组合表格实战用 createTableHook 构建共享特性与可复用渲染组件的应用级表格工厂TanStack Lit Table 可组合表格实战用 createTableHook 构建共享特性与可复用渲染组件的应用级表格工厂 导读 createTab前端UI组件TanStack Table 的 createTableHook 完全指南为 Octane 打造可复用、类型安全的表格工厂TanStack Table 的 createTableHook 完全指南为 Octane 打造可复用、类型安全的表格工厂 createTableHook 是前端UI组件Preact 表格组合式架构实战用 createTableHook 打造可复用的应用级表格工厂Preact 表格组合式架构实战用 createTableHook 打造可复用的应用级表格工厂 createTableHook 是 tanstack/pre前端UI组件上一篇突破版本管理瓶颈Changesets定制化开发全指南下一篇游戏手柄兼容性难题终结者AntimicroX手柄映射工具完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考