资讯详情

资讯详情

TanStack Table React 的 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 Table React 提供的一个应用级表格工厂函数用于把表格功能features、行模型row models、默认配置和可复用组件一次性定义好再通过返回的useAppTable为每张业务表注入各自的columns与data。阅读本文后你将掌握createTableHook的完整签名、全部配置项、返回值语义以及它在 组合式表格示例 中的真实落地方式并了解其底层 Context 注入与 React Compiler 兼容的实现原理。createTableHook 是什么createTableHook是 TanStack Table React 在tanstack/react-table包中提供的一个高阶工厂函数它相当于 TanStack Form 中createFormHook在表格领域的对应物。官方参考文档 createTableHook 将其定位为Creates a custom table hook with pre-bound components for composition.其核心能力可概括为五点一次性定义 features、row models 和默认 options并在所有表格之间共享注册可复用的 table、cell、header 组件在这些组件内部通过 Context 访问 table / cell / header 实例获得一个返回「扩展表格带 App 包装组件」的useAppTablehook获得一个预先绑定好 features 的createAppColumnHelper函数。与其对应的独立用法useTable相比createTableHook更适合「应用中存在多张表格、且需要共享功能与 UI 约定」的场景而一次性使用的单张表格则可以直接使用useTable。官方指南 Composable Tables (createTableHook) Guide 也明确建议先以共享 options 与 features 起步仅当应用需要标准化的表格 UI 片段时再引入组件注册机制。函数签名与类型参数createTableHook的 TypeScript 签名定义于 packages/react-table/src/createTableHook.tsx:692function createTableHook TFeatures extends TableFeatures, const TTableComponents extends Recordstring, ComponentTypeany, const TCellComponents extends Recordstring, ComponentTypeany, const THeaderComponents extends Recordstring, ComponentTypeany, (__namedParameters: CreateTableHookOptionsTFeatures, TTableComponents, TCellComponents, THeaderComponents): CreateTableHookResultTFeatures, TTableComponents, TCellComponents, THeaderComponents;类型参数说明类型参数约束含义TFeatures继承TableFeatures通过tableFeatures({...})组装的功能集包括各 Feature 与 row model 工厂TTableComponentsRecordstring, ComponentTypeany注册的表格级组件映射const修饰确保字面量类型被精确推断TCellComponentsRecordstring, ComponentTypeany注册的单元格级组件映射THeaderComponentsRecordstring, ComponentTypeany注册的表头/表尾级组件映射注意源码中组件映射类型参数均带const修饰createTableHook.tsx:694-696这意味着传入的对象字面量会按最精确的字面量类型推断注册的组件名称如PaginationControls、TextCell、SortIndicator会成为返回类型中的具名字段从而获得完整的类型提示。参数CreateTableHookOptions参数是一个对象类型为 CreateTableHookOptions其定义如下type CreateTableHookOptionsTFeatures, TTableComponents, TCellComponents, THeaderComponents OmitTableOptionsTFeatures, any, columns | data | store | state | initialState { tableComponents?: TTableComponents cellComponents?: TCellComponents headerComponents?: THeaderComponents tableContext?: ContextReactTableany, any cellContext?: ContextCellany, any, any headerContext?: ContextHeaderany, any, any }它继承了TableOptions的除columns、data、store、state、initialState之外的全部字段——也就是说你可以在createTableHook调用处设置默认的排序、过滤、分页等行为选项但每张表的列、数据与状态仍由各表调用useAppTable时自行提供。各字段含义如下features通过tableFeatures({...})组装的功能集是所有由该 hook 创建的表格共享的核心配置同时也是唯一被要求通过Omit之外的常规途径传入、并会在返回的 column helper 中继续绑定的配置。tableComponents?表格级组件需要访问 table 实例直接挂载在useAppTable返回的 table 对象上组件内部使用useTableContext()读取实例。示例{ PaginationControls, GlobalFilter, RowCount }。cellComponents?单元格级组件需要访问 cell 实例可在AppCell的 children 参数中通过cell.TextCell等方式使用组件内部使用useCellContext()。示例{ TextCell, NumberCell, DateCell, CurrencyCell }。headerComponents?表头/表尾级组件需要访问 header 实例可在AppHeader/AppFooter的 children 参数中通过header.SortIndicator等使用组件内部使用useHeaderContext()。示例{ SortIndicator, ColumnFilter, ResizeHandle }。tableContext?自定义的 table React Context。默认使用模块级共享 Context仅当需要隔离不同表格的 Context例如在一张表格内嵌套另一张表格时才传入通过createContext自行创建的 Context。cellContext?自定义的 cell Context语义同tableContext。headerContext?自定义的 header Context供headerComponents与表尾组件使用语义同tableContext。在源码实现中这三个 Context 的默认值来自模块顶部定义的共享 ContextcreateTableHook.tsx:34-38const sharedTableContext createContextReactTableany, any | null(null) const sharedCellContext createContextCellany, any, any | null(null) const sharedHeaderContext createContextHeaderany, any, any | null(null)并且在函数解构参数中作为默认值使用createTableHook.tsx:697-704export function createTableHookTFeatures, ...({ tableComponents, cellComponents, headerComponents, tableContext sharedTableContext as ContextReactTableany, any, cellContext sharedCellContext as ContextCellany, any, any, headerContext sharedHeaderContext as ContextHeaderany, any, any, ...defaultTableOptions }: CreateTableHookOptions...) {返回值CreateTableHookResult返回值类型为 CreateTableHookResult包含六个成员成员签名作用appFeaturesTFeatures传入createTableHook的 features 对象createAppColumnHelperTData() AppColumnHelper...预先绑定TFeatures与组件类型的列辅助器cell/header/footer 渲染属性可直接拿到绑定组件useAppTableTData, TSelected(tableOptions, selector?) AppReactTable...创建表格的 hookTData从data选项自动推断返回带App*包装组件与注册tableComponents的扩展表格useTableContextTData, TSelected() AppReactTable...在最近的table.AppTable内读取表格实例即useAppTable返回的同一扩展实例useCellContextTValue() Cell TCellComponents { FlexRender }在最近的table.AppCell内读取 cell扩展了注册组件与 Context 绑定的FlexRenderuseHeaderContextTValue() Header THeaderComponents { FlexRender }在最近的table.AppHeader/table.AppFooter内读取 header扩展同上关于useTableContext的类型推断有一个重要细节React Context 无法携带 Provider 的泛型因此TSelected无法自动推断默认取完整的TableStateTFeatures。只有在useAppTable传入了 selector 且需要精确的切片类型时才需要显式传入TSelected以匹配 selector 的选择结果。最小可用示例共享 features 与默认选项参考文档给出了一个完整的端到端示例。我们首先看「仅共享 features 与 options」的最小形态对应指南 Start With Shared Features and Optionsimport { createSortedRowModel, createTableHook, rowSortingFeature, sortFns, tableFeatures, } from tanstack/react-table const features tableFeatures({ rowSortingFeature, sortedRowModel: createSortedRowModel(), sortFns, }) const { useAppTable, createAppColumnHelper } createTableHook({ features, debugTable: true, enableSortingRemoval: false, })这里debugTable: true与enableSortingRemoval: false作为默认选项会作用于所有通过useAppTable创建的表格。features还会绑定到返回的 column helper 上使列定义能够感知排序 API 的存在。随后为每种行类型创建列辅助器type 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, }), ])最后通过useAppTable创建表格并为不同表格覆盖默认配置function UsersTable({ data }: { data: Person[] }) { const table useAppTable( { key: users-table, columns, data, }, (state) ({ sorting: state.sorting }), ) // 使用 table 实例渲染 } // 个别表格可覆盖共享默认值enableSortingRemoval 被覆盖为 true const table useAppTable( { key: sortable-users-table, columns, data, enableSortingRemoval: true, }, (state) ({ sorting: state.sorting }), )覆盖规则是useAppTable传入的选项优先于createTableHook的默认选项。源码中通过对象展开合并实现createTableHook.tsx:951-958const table useTableTFeatures, TData, TSelected( { ...defaultTableOptions, ...tableOptions } as TableOptionsTFeatures, TData, selector, )即便不注册任何组件useAppTable返回的表格也完全兼容useTable的常规渲染方式——直接使用table.getHeaderGroups()、table.getRowModel()与table.FlexRender /即可不需要AppTable、AppCell、AppHeader或注册组件见指南 Render With The Normal Table APIs。完整示例组件注册与 App 包装组件参考文档中的完整示例同时演示了组件注册机制。该示例与仓库中的 组合式表格示例 高度一致——示例里 Users 与 Products 两张表共享src/hooks/table.ts与全部可复用组件。1. 创建应用级 table hook组件注册表在 examples/react/composable-tables/src/hooks/table.ts 中一次性完成 features、row models、默认选项与三类组件的注册export const { createAppColumnHelper, useAppTable, useTableContext, useCellContext, useHeaderContext, } createTableHook({ // 功能与行模型在此一次性定义所有表共享 features: tableFeatures({ columnFilteringFeature, rowPaginationFeature, rowSelectionFeature, rowSortingFeature, sortedRowModel: createSortedRowModel(), filteredRowModel: createFilteredRowModel(), paginatedRowModel: createPaginatedRowModel(), filterFns: { includesString: filterFn_includesString, inNumberRange: filterFn_inNumberRange, }, sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text, }, }), // 默认表格选项也可以在这里设置 getRowId: (row) row.id, // 表格级组件通过 table.ComponentName 直接使用 tableComponents: { PaginationControls, RowCount, TableToolbar }, // 单元格级组件通过 AppCell children 中的 cell.ComponentName 使用 cellComponents: { SelectCell, TextCell, NumberCell, StatusCell, ProgressCell, RowActionsCell, PriceCell, CategoryCell }, // 表头/表尾级组件通过 AppHeader/AppFooter children 中的 header.ComponentName 使用 headerComponents: { SortIndicator, ColumnFilter, FooterColumnId, FooterSum }, })参考文档的示例中还展示了 features 的另一种组装方式——把 row model 工厂与sortFns、filterFns直接传入features: tableFeatures({ rowPaginationFeature, rowSortingFeature, columnFilteringFeature, paginatedRowModel: createPaginatedRowModel(), sortedRowModel: createSortedRowModel(), filteredRowModel: createFilteredRowModel(), sortFns, filterFns, }),两种写法等价实际项目中可按习惯选择。[!IMPORTANT]useTableContext、useCellContext、useHeaderContext必须从调用createTableHook的同一模块导入如上所示。这些 hook 携带了你的TFeatures与组件映射类型因此table.PaginationControls、cell.TextCell、header.SortIndicator等都能获得完整类型。详见 Table Context 指南当需要隔离嵌套表格的 Context 时可用createTableHookContexts创建作用域 Context 传入createTableHook。2. 定义三类可复用组件表格级组件使用useTableContext()读取 table 实例。以 table-components.tsx 中的PaginationControls为例export function PaginationControls() { const table useTableContext() return ( div classNamepagination button onClick{() table.firstPage()} disabled{!table.getCanPreviousPage()} {} /button button onClick{() table.previousPage()} disabled{!table.getCanPreviousPage()} {} /button button onClick{() table.nextPage()} disabled{!table.getCanNextPage()} {} /button button onClick{() table.lastPage()} disabled{!table.getCanLastPage()} {} /button span Page{ } strong {(table.state.pagination.pageIndex 1).toLocaleString()} of {table.getPageCount().toLocaleString()} /strong /span span | Go to page: input typenumber min1 max{table.getPageCount()} defaultValue{table.state.pagination.pageIndex 1} onChange{(e) { const page e.target.value ? Number(e.target.value) - 1 : 0 table.setPageIndex(page) }} / /span select value{table.state.pagination.pageSize} onChange{(e) table.setPageSize(Number(e.target.value))} {[10, 20, 30, 40, 50].map((pageSize) ( option key{pageSize} value{pageSize} Show {pageSize} /option ))} /select /div ) }单元格级组件使用useCellContextTValue()读取 cell。以 cell-components.tsx 为例TextCell与NumberCell分别针对字符串与数字提供格式化渲染export function TextCell() { const cell useCellContextstring() return span{cell.getValue()}/span } export function NumberCell() { const cell useCellContextnumber() return span{cell.getValue().toLocaleString()}/span } export function PriceCell() { const cell useCellContextnumber() return ( span classNameprice ${cell.getValue().toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} /span ) }表头/表尾级组件使用useHeaderContext()读取 header。以 header-components.tsx 中的SortIndicator与FooterSum为例export function SortIndicator() { const header useHeaderContext() const sorted header.column.getIsSorted() if (!sorted) return null return span classNamesort-indicator{sorted asc ? : }/span } export function FooterSum() { const header useHeaderContext() const table header.getContext().table const rows table.getFilteredRowModel().rows const sum rows.reduce((acc, row) { const value row.getValue(header.column.id) return acc (typeof value number ? value : 0) }, 0) return span classNamefooter-sum{sum 0 ? sum.toLocaleString() : —}/span }3. 定义列引用预绑定组件列定义通过createAppColumnHelper创建cell/header/footer 的渲染属性中可以直接引用已注册组件参考文档原例// 创建列辅助器TFeatures 已绑定 const columnHelper createAppColumnHelperPerson() const columns [ columnHelper.accessor(firstName, { header: First Name, cell: ({ cell }) cell.TextCell /, // cell 上已有预绑定组件 }), columnHelper.accessor(age, { header: Age, cell: ({ cell }) cell.NumberCell /, }), ]4. 用 App 包装组件渲染表格useAppTable返回的扩展表格带有AppTable、AppHeader、AppCell、AppFooter四个包装组件以及挂载在 table 对象上的tableComponents。参考文档的完整渲染代码如下function UsersTable({ data }: { data: Person[] }) { const table useAppTable({ columns, data, // TData 从 Person[] 自动推断 }) return ( table.AppTable table thead {table.getHeaderGroups().map((headerGroup) ( tr key{headerGroup.id} {headerGroup.headers.map((h) ( table.AppHeader header{h} key{h.id} {(header) ( th table.FlexRender header{h} / header.SortIndicator / /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.TextCell / /td )} /table.AppCell ))} /tr ))} /tbody /table table.PaginationControls / /table.AppTable ) }在更丰富的 main.tsx 实现中还可以给AppTable、AppHeader、AppCell、AppFooter传入可选的selector实现状态切片订阅。例如table.AppTable selector{(state) ({ pagination: state.pagination, sorting: state.sorting, columnFilters: state.columnFilters, })} {({ sorting, columnFilters }) ( // children 函数接收所选状态切片 div classNametable-container table.TableToolbar titleUsers Table onRefresh{refreshData} / {/* ...thead/tbody 渲染同前... */} table.PaginationControls / table.RowCount / /div )} /table.AppTableAppCell与AppHeader同样支持 selectorchildren 会额外收到选中状态// AppCell 带 selectorchildren 收到 cell 与 columnFilters 切片 table.AppCell cell{cell} selector{(s) s.columnFilters} {(c, filters) td{filters.length}/td} /table.AppCell // AppHeader 带 selectorchildren 收到 header 与 sorting 切片 table.AppHeader header{header} selector{(s) s.sorting} {(h, sorting) th{sorting.length} sorted/th} /table.AppHeader由于AppTable/AppCell/AppHeader/AppFooter都支持「无 selectorchildren 为 ReactNode 或单参数渲染函数」与「有 selectorchildren 接收状态切片」两种形态同一组件 API 可以根据需要灵活切换订阅粒度。源码级原理Context 注入与扩展实例的构建createTableHook的运行时实现createTableHook.tsx:692-1266值得深入理解它揭示了「预绑定组件」与「App 包装组件」是如何在 React 中落地为真实行为的。默认选项合并与 tableRef 稳定性设计useAppTable首先调用底层的useTable合并默认选项随后有一段非常关键的设计createTableHook.tsx:960-968// useTable 每次渲染都会返回全新的 table 引用这是 React Compiler 的要求。 // App 包装组件绝不能依赖该引用否则每次渲染都会被重新创建 // React 会在每次状态更新时重挂载整个子树例如工具栏中的受控输入会在每次按键时失去焦点。 // 因此组件保持稳定只创建一次并通过 ref 读取当前 table。 const tableRef useRef(table) tableRef.current table这正是该实现与 React Compiler 兼容的关键AppTable、AppCell、AppHeader、AppFooter四个组件都通过useMemo(() {...}, [])只创建一次空依赖数组内部通过tableRef.current读取最新表格实例。注释中特别点明了动机——如果包装组件依赖每次渲染都变化的 table 引用受控输入如工具栏搜索框、列过滤输入框会在每次击键后因子树重挂载而丢失焦点。Object.assign 扩展实例AppCell在渲染时把cellComponents与一个 Context 绑定的FlexRender合并到 cell 实例上createTableHook.tsx:1042-1047const { cell, children, selector: appCellSelector } props as any const currentTable tableRef.current const extendedCell Object.assign(cell, { FlexRender: CellFlexRender, ...cellComponents, })AppHeader/AppFooter对 header 做同样的处理createTableHook.tsx:1118-1123、createTableHook.tsx:1198-1203。CellFlexRender/HeaderFlexRender/FooterFlexRender这三个「上下文感知的 FlexRender」组件createTableHook.tsx:901-926内部直接通过useCellContext()/useHeaderContext()取实例因此使用时无需再传cell/header/footerprop。最后useAppTable把四个 App 包装组件与tableComponents通过Object.assign挂到 table 实例上createTableHook.tsx:1238-1253const extendedTable useMemo(() { return Object.assign(table, { AppTable, AppCell, AppHeader, AppFooter, ...tableComponents, }) as AppReactTable... }, [table, AppTable, AppCell, AppHeader, AppFooter])这也是为什么useTableContext返回的实例与useAppTable返回的是「同一个扩展实例」——table.AppTable的 Provider 提供的 value 就是这份Object.assign之后的扩展表格见 createTableHook.tsx:807-817 的注释与类型断言。Context hook 的边界校验useTableContext、useCellContext、useHeaderContext在读取不到实例时会抛出明确的错误信息createTableHook.tsx:800-805useTableContext must be used within an AppTable component. Make sure your component is wrapped with table.AppTable.../table.AppTable.useCellContext与useHeaderContext也有对应的错误提示分别要求组件位于table.AppCell cell{cell}与table.AppHeader/table.AppFooter之内。这能帮助开发者在误用时快速定位问题。createAppColumnHelper 的类型绑定createAppColumnHelper的运行时实现只是把 core 的createColumnHelper做了类型断言createTableHook.tsx:745-759function createAppColumnHelperTData extends RowData(): AppColumnHelper... { // 运行时实现相同——组件在渲染时挂载此断言为列定义提供增强类型 return coreCreateColumnHelperTFeatures, TData() as AppColumnHelper... }也就是说「预绑定」的本质是类型层面的绑定运行时列辅助器与 core 版本完全一致组件真正的挂载发生在AppCell/AppHeader/AppFooter的Object.assign阶段。这一点从源码注释可以得到确认// The runtime implementation is the same - components are attached at render time。常见陷阱与实战建议Context hook 必须从调用createTableHook的同一模块导入。否则会丢失TFeatures与组件映射类型table.PaginationControls、cell.TextCell、header.SortIndicator等将失去类型提示。受控输入与 React CompileruseAppTable每次渲染返回新 table 引用但 App 包装组件保持稳定并通过tableRef读取最新实例因此工具栏、过滤输入等受控组件不会因重挂载而失焦。在自定义组件中需要订阅外部状态源时可参考 SelectCell 的实现通过Subscribe source{table.atoms.rowSelection}包裹或 ColumnFilter 的实现通过useSelector(table.store, ...)订阅。嵌套表格需要作用域 Context默认的共享 Context 在多套createTableHook嵌套时会互相干扰此时应使用createTableHookContexts创建作用域 Context 并传入createTableHook详见 Table Context 指南。覆盖默认值的粒度useAppTable的选项优先于createTableHook的默认值仅个别表格需要不同行为时直接在调用处覆盖即可无需创建新的 app hook。何时使用该模式多张表格需要共享 features、row models、默认选项或 UI 约定时使用createTableHook一次性使用的单张表格直接用useTable组件注册机制仅在需要标准化可复用表格 UI 片段时启用见指南 When To Use This Pattern。相关资源createTableHook 参考文档CreateTableHookOptions 类型参考CreateTableHookResult 接口参考Composable Tables 官方指南Table Context 指南实现源码组合式表格完整示例赞分享前端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构建可复用、组件预绑定的表格工厂 导读 createTableHook 是 tans前端UI组件TanStack Solid Table 可组合表格实战用 createTableHook 构建共享特性与可复用组件的高阶表格工厂TanStack Solid Table 可组合表格实战用 createTableHook 构建共享特性与可复用组件的高阶表格工厂 导读 在大型 SolidJ前端UI组件tanstack/angular-table 的 createTableHook构建应用级可组合表格与预绑定组件注册表tanstack/angular table 的 createTableHook构建应用级可组合表格与预绑定组件注册表 导读 createTableHook前端UI组件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →