尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深入理解 AppPreactTable:Preact Table 的 App 组件扩展类型与组合式表格体系

深入理解 AppPreactTable:Preact Table 的 App 组件扩展类型与组合式表格体系 前端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点击查看免费下载AppPreactTable是 TanStack Table Preact 适配层中由useAppTable返回的扩展表格类型它在标准 PreactTable 实例之上挂载了AppTable、AppCell、AppHeader、AppFooter四个包装组件以及注册的 tableComponents用于在 Preact 应用中构建共享特性、共享组件注册表的组合式表格。阅读本文后你将掌握这一类型的完整结构、六个泛型参数的含义以及如何基于createTableHookuseAppTable写出类型安全、可复用的应用级表格。类型定义从 PreactTable 到 AppPreactTableAppPreactTable的类型别名定义如下来源createTableHook.tsxtype AppPreactTableTFeatures, TData, TSelected, TTableComponents, TCellComponents, THeaderComponents PreactTableTFeatures, TData, TSelected NoInferTTableComponents object;它的构成可以拆解为三部分PreactTableTFeatures, TData, TSelecteduseTable返回的基础表格类型它Omit掉 core 中Table的原始store字段并补充了 Preact 特有的SubscribeHOC、FlexRender组件以及state只读状态useTable.ts。table.state的类型由第二个参数传入的 selector 决定可能与完整表格状态结构不一致。NoInferTTableComponents通过createTableHook注册的表格级组件被交叉进表格实例且在类型层面使用NoInfer避免泛型推断干扰。这意味着table.PaginationControls、table.RowCount这类自定义组件会直接出现在表格对象上类型完全可感知。object携带四个 App 包装组件成员——AppTable、AppCell、AppHeader、AppFooter。文档对该类型的定位是一句话Extended table API returned by useAppTable with all App wrapper components即useAppTable返回的、附带全部 App 包装组件的扩展表格 API。App 组件体系四个包装组件各自解决什么问题四个 App 包装组件本质上都是Provider 可选 Subscribe的组合它们把表格、单元格、表头实例写入 Preact Context同时把注册的组件绑定到实例对象上让子组件通过上下文读取实例并直接渲染注册组件。下面逐一展开。AppTable根级上下文 ProviderAppTable: AppTableComponentTFeatures;AppTable是根包装组件负责提供表格上下文并可选地接受 selector 实现订阅Subscribe 功能。它的两种用法无 selector / 有 selector如下// Without selector - children is ComponentChildren table.AppTable table.../table /table.AppTable // With selector - children receives selected state table.AppTable selector{(s) s.pagination} {(pagination) divPage {pagination.pageIndex}/div} /table.AppTable从源码看createTableHook.tsxAppTableImpl的核心逻辑是用TableContext.Provider包裹 childrenvalue 为tableRef.current当前表格实例若传入了 selector则用currentTable.Subscribe selector{...}订阅表格 store并把选中状态作为参数传给函数式 children若未传 selectorchildren 直接作为普通 JSX 子节点渲染。这里的关键实现细节是tableRef 模式useTable每次渲染都会返回新的 table 引用这是 React Compiler 兼容性的要求因此 App 包装组件不能依赖该引用否则每次状态更新都会导致整个子树被重建典型症状是工具栏受控输入框每次按键都失焦。源码通过useRef保存当前 table 并每次渲染刷新tableRef.current而AppTable本身用useMemo(..., [])创建一次、保持稳定。AppCell单元格上下文 预绑定 cellComponentsAppCell: AppCellComponentTFeatures, TData, NoInferTCellComponents;AppCell包装一个单元格提供带预绑定cellComponents的单元格上下文同样可选 selector。文档给出两种用法// Without selector table.AppCell cell{cell} {(c) tdc.TextCell //td} /table.AppCell // With selector - children receives cell and selected state table.AppCell cell{cell} selector{(s) s.columnFilters} {(c, filters) td{filters.length}/td} /table.AppCell实现上createTableHook.tsxAppCellImpl做了三件事通过Object.assign(cell, { FlexRender: CellFlexRender, ...cellComponents })把注册的 cellComponents 和上下文绑定的FlexRender直接挂到 cell 实例上——这就是 children 里c.TextCell、c.FlexRender的来源用CellContext.Provider提供原始 cell未扩展的实例使useCellContext()可读取有 selector 时用currentTable.Subscribe订阅并将选中状态作为 children 的第二个参数传入。这个扩展后的上下文类型对应文档中的 AppCellContextcell字段类型为CellTFeatures, TData, TValue TCellComponents { FlexRender: () ComponentChildren }同时保留column、getValue、renderValue、row、table等标准字段。AppHeader表头上下文 预绑定 headerComponentsAppHeader: AppHeaderComponentTFeatures, TData, NoInferTHeaderComponents;AppHeader包装一个表头Header提供带预绑定headerComponents的表头上下文// Without selector table.AppHeader header{header} {(h) thh.SortIndicator //th} /table.AppHeader // With selector table.AppHeader header{header} selector{(s) s.sorting} {(h, sorting) th{sorting.length} sorted/th} /table.AppHeader实现逻辑与AppCell对称createTableHook.tsxObject.assign(header, { FlexRender: HeaderFlexRender, ...headerComponents })后通过HeaderContext.Provider提供有 selector 时走 Subscribe。对应的扩展上下文类型见 AppHeaderContextheader字段为HeaderTFeatures, TData, TValue THeaderComponents { FlexRender }外加column与table。AppFooter表尾的复用包装AppFooter: AppHeaderComponentTFeatures, TData, NoInferTHeaderComponents;值得注意AppFooter的类型与AppHeader完全一致——因为表尾在 TanStack Table 中同样使用Header实例表达。它包装一个表尾提供带预绑定headerComponents的表头上下文表尾组件复用 headerComponents 注册表table.AppFooter header{footer} {(f) tdtable.FlexRender footer{footer} //td} /table.AppFooter从源码看createTableHook.tsxAppFooterImpl与AppHeaderImpl结构相同唯一的差异是把FlexRender绑定为FooterFlexRender内部读取 footer 并调用FlexRender footer{header} /。这也是文档注释Wraps a footer and provides header context with pre-bound headerComponents的由来。六个类型参数详解泛型参数约束含义TFeaturesextends TableFeatures该表启用的特性集合如 rowSortingFeature、rowPaginationFeature由createTableHook的features选项固化TDataextends RowData行数据类型通常由useAppTable的data选项自动推断TSelected无约束通过useAppTable第二个参数selector选中的状态切片类型默认TableStateTFeaturesTTableComponentsextends Recordstring, ComponentTypeany注册的表格级组件映射交叉进表格实例本身TCellComponentsextends Recordstring, ComponentTypeany注册的单元格级组件映射绑定到 cell 实例THeaderComponentsextends Recordstring, ComponentTypeany注册的表头/表尾级组件映射绑定到 header 实例组件映射统一以Recordstring, ComponentTypeany约束意味着你在createTableHook中传入的任何 Preact 组件对象都会获得完整的类型感知。TData由useAppTable从数据推断这正是每个表只关心自己的列和数据这一组合式设计的关键。源码视角useAppTable 如何组装扩展表格useAppTable是createTableHook返回的核心 hookcreateTableHook.tsx其组装过程可分为四步合并默认选项{ ...defaultTableOptions, ...tableOptions }——createTableHook传入的选项含features成为默认值useAppTable调用处传入的选项优先覆盖调用 useTable内部委托useTableTFeatures, TData, TSelected构建基础表格selector 原样传递控制table.state的类型与订阅范围稳定化包装组件AppTable、AppCell、AppHeader、AppFooter各自通过useMemo(..., [])只创建一次内部一律通过tableRef.current读取最新表格避免状态更新引发的子树重挂载合并扩展成员Object.assign(table, { AppTable, AppCell, AppHeader, AppFooter, ...tableComponents })得到最终的AppPreactTable实例。同时createTableHook还会返回createAppColumnHelper预绑定TFeatures与注册组件的列辅助器、useTableContext/useCellContext/useHeaderContext从对应 Provider 读取实例的钩子。注册组件内部正是通过这三个上下文钩子访问实例的例如function PaginationControls() { const table useTableContext() return ( table.Subscribe selector{(s) s.pagination} {(pagination) ( div button onClick{() table.previousPage()}Prev/button spanPage {pagination.pageIndex 1}/span button onClick{() table.nextPage()}Next/button /div )} /table.Subscribe ) } function TextCell() { const cell useCellContextstring() return span{cell.getValue()}/span } function SortIndicator() { const header useHeaderContext() const sorted header.column.getIsSorted() return sorted asc ? : sorted desc ? : null }完整实战组合式表格的三种典型写法写法一仅共享特性与默认选项不使用 App 组件最小化的createTableHook只做特性共享返回的表格仍可用标准useTable的渲染方式输出。先在模块级创建工厂composable-tables 指南import { createSortedRowModel, createTableHook, rowSortingFeature, sortFns, tableFeatures, } from tanstack/preact-table const features tableFeatures({ rowSortingFeature, sortedRowModel: createSortedRowModel(), sortFns, }) const { useAppTable, createAppColumnHelper } createTableHook({ features, debugTable: true, enableSortingRemoval: false, }) const columnHelper createAppColumnHelperPerson() const columns columnHelper.columns([ columnHelper.accessor(firstName, { cell: (info) info.getValue() }), columnHelper.accessor(age, { header: Age }), ])随后useAppTable返回的实例拥有AppTable等成员但你也可以完全忽略它们用普通表格 API 渲染function UsersTable({ data }: { data: Person[] }) { const table useAppTable( { key: users-table, columns, data }, (state) ({ sorting: state.sorting }), ) return ( 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 ) }写法二注册组件 App 组件渲染推荐当多个表格需要共享工具栏、单元格渲染器、表头渲染器时把组件注册进createTableHook。完整示例见 examples/preact/composable-tables其共享配置集中在src/hooks/table.tsconst { useAppTable, createAppColumnHelper, 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 }, })列定义中直接引用注册组件cell/header/footer渲染函数的参数即AppCellContext/AppHeaderContext因此cell.TextCell、props.column.id都有完整类型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 /, }), ]), [], )渲染时useAppTable返回的表格对象上直接出现table.AppTable、table.AppHeader、table.AppCell、table.TableToolbar、table.PaginationControls——这正是AppPreactTable类型所描述的结构const table useAppTable( { key: users-table, columns, data, debugTable: true }, (state) state, ) return ( 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 )注意table.AppTable的 selector 一次订阅了pagination、sorting、columnFilters三个切片函数式 children 直接解构使用而AppHeader/AppCell内部的注册组件通过上下文钩子各自消费实例无需手动传递。写法三嵌套隔离场景下的 scoped context默认情况下createTableHook使用模块级共享 ContextHMR 稳定兄弟表格之间天然隔离。但若需要嵌套两个不同配置的表格例如外层表格的表头组件里渲染内层表格共享 Context 会让内层消费者静默解析到最近的外层 Provider。此时使用createTableHookContexts创建独立上下文并传入const features tableFeatures({ rowSelectionFeature }) const { tableContext, cellContext, headerContext } createTableHookContextstypeof features() export const app createTableHook({ features, tableContext, cellContext, headerContext, })最佳实践与常见陷阱模块级创建工厂不要在渲染函数内创建。createTableHook应放在模块作用域在组件内调用会让 hook 配置与组件注册表每次渲染都不稳定导致整个表格子树反复重建。不要为单张表抽象。工厂的价值在于集中重复的策略特性、行模型、默认选项、组件约定。只有一张表时应直接用useTable({ features, columns, data })。上下文钩子必须在对应包装组件内使用。useTableContext必须在table.AppTable内、useCellContext必须在table.AppCell内、useHeaderContext必须在table.AppHeader/table.AppFooter内否则会抛出运行时错误源码中均有显式检查与错误信息。把钩子从调用createTableHook的同一模块导入。只有那里返回的useTableContext/useCellContext/useHeaderContext才携带你的TFeatures与注册组件映射从而让table.PaginationControls、cell.TextCell、header.SortIndicator获得完整类型推断。熟悉配套类型。与AppPreactTable协同工作的类型还包括 AppColumnHelper、AppColumnDefBase、CreateTableHookOptions 以及createTableHook函数文档docs/framework/preact/reference/functions/createTableHook.md组合阅读可以完整覆盖从创建工厂到定义列再到渲染表格的全链路类型契约。赞分享前端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点击查看免费下载相关推荐TextPathView性能优化5个技巧解决多View同时动画卡顿问题TextPathView性能优化5个技巧解决多View同时动画卡顿问题 TextPathView是一款强大的Android文字路径动画库能够为应用添加炫酷的前端UI组件tanstack/alpine-table 中 AppColumnHelper 类型详解Alpine 组合式表格的列定义类型契约tanstack/alpine table 中 AppColumnHelper 类型详解Alpine 组合式表格的列定义类型契约 本文以 tanstack前端UI组件TDesign Vue Next 表格组件类型扩展问题解析TDesign Vue Next 表格组件类型扩展问题解析 在使用 TDesign Vue Next 的 Table 组件时开发者经常会遇到类型定义上的困惑。前端UI组件桌面应用上一篇Apache PredictionIO与TensorFlow集成深度学习模型部署下一篇告别乱序3种方案实现UUID高效排序ramsey/uuid实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表