实战指南:从子行数据到自定义详情面板)
前端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/octane-table的官方文档 docs/framework/octane/guide/expanding.md 为骨架完整讲解行展开Expanding特性的配置、两种典型使用场景、展开状态管理、与过滤/分页/排序等特性的协作以及服务端手动展开模式。读完本文你将掌握在 Octane 中通过rowExpandingFeaturecreateExpandedRowModel搭建层级数据表格、实现自定义详情面板、用外部 atom 管理展开状态并正确组合filterFromLeafRows、paginateExpandedRows、autoResetExpanded等选项的完整方案。一、Expanding 是什么两种典型使用场景Expanding行展开允许用户显示或隐藏与某一父行相关联的额外行数据。它主要服务于两类需求展开子行数据数据本身是层级结构的子行、聚合行等用户可以从高层向下钻取。例如树形组织结构、分类目下的明细数据。展开自定义 UI展开后显示与行相关的额外信息常见实现形态包括可展开行expandable rows、详情面板detail panels、子组件sub-components等。这类 UI 不一定要与表格列结构保持一致。仓库中 examples/octane/expanding 是一个同时演示了子行展开、过滤、分页、排序、行选择等特性协同的完整示例其中Person数据模型makeData.ts通过嵌套的subRows字段递归构造了 3 层数据如makeData(100, 5, 3)生成 100 个根行、每行 5 个子行、每个子行 3 个孙行是理解层级展开的最佳参考。二、Expanding 基础搭建features 与 useTable 配置2.1 最小配置启用行展开在 Octane 中所有特性都通过tableFeatures组装再交给useTable。启用行展开需要两步在tableFeatures中加入rowExpandingFeature如果使用客户端展开还需在同一处注册expandedRowModel: createExpandedRowModel()row model 插槽是类型检查的必须在 feature 之后配置。import { useTable, tableFeatures, rowExpandingFeature, createExpandedRowModel, } from tanstack/octane-table const features tableFeatures({ rowExpandingFeature, expandedRowModel: createExpandedRowModel(), // if using client-side expanding // manualExpanding: true, // if using manual server-side expanding }) const table useTable({ features, columns, data, })添加rowExpandingFeature后相关的行级与表级 API 便会注入。从源码 rowExpandingFeature.ts 可以看到该 feature 通过getInitialState提供默认状态expanded: getDefaultExpandedState()一个空 map即默认不展开任何行并通过assignRowPrototype与constructTableAPIs分别挂载行级和表级 API。2.2 createExpandedRowModel 的底层行为createExpandedRowModel返回一个工厂函数其内部通过tableMemo做记忆化依赖table.atoms.expanded、getPreExpandedRowModel()、paginateExpandedRows、manualPagination等输入见 createExpandedRowModel.ts。核心逻辑expandRows采用深度优先遍历遇到有subRows且处于展开状态的父行时把子行插入扁平化后的行序列中同时保留原层级关系row.subRows与parentId结构不变。这意味着渲染时无需递归直接遍历getRowModel().rows即可得到父行 已展开子行的扁平列表——这也是行展开与分组grouping在渲染方式上的关键差异之一。另外注意当expanded为true全展开或 map 中没有任何 key 时_createExpandedRowModel会直接返回未展开的 row model避免无意义遍历当paginateExpandedRows: false且非手动分页时也会提前返回保证展开行为由分页阶段接管。三、客户端展开的两类数据形态客户端展开的数据可以是表格行也可以是任意自定义数据二者的处理方式不同。3.1 形态一表格行作为展开数据getSubRows如果数据对象本身包含嵌套子行数据就用getSubRows告诉表格子行放在哪个字段。以下面的层级数据为例type Person { id: number name: string age: number children?: Person[] | undefined } const data: Person[] [ { id: 1, name: John, age: 30, children: [ { id: 2, name: Jane, age: 5 }, { id: 5, name: Jim, age: 10 }, ], }, { id: 3, name: Doe, age: 40, children: [{ id: 4, name: Alice, age: 10 }], }, ]然后在useTable中指定getSubRowsconst table useTable({ features, getSubRows: (row) row.children, // return the children array as sub-rows // other options... })表格实例会据此在每行上寻找子行。示例工程 main.tsrx 中正是通过getSubRows: (row) row.subRows把递归生成的subRows交给表格。[!NOTE]getSubRows可以是任意复杂的函数但要牢记它会对每一行、每一个子行执行。函数不够优化时成本会很高且不支持异步函数。如果数据量较大建议在数据层预先将子行挂到统一字段如示例中的subRows让getSubRows退化为 O(1) 的字段读取。3.2 形态二自定义展开 UIgetRowCanExpand 与详情面板很多场景下你想展示的是行的额外详情而非同构的子行数据这类 UI 常被称为 detail panels 或 sub-components。默认情况下row.getCanExpand()只有在行上发现了subRows时才返回true因此需要覆盖getRowCanExpand来决定哪些行可以展开import { Fragment } from octane //... const table useTable({ features, getRowCanExpand: (row) true, // Add your logic to determine if a row can be expanded. True means all rows include expanded data // other options... }) //... tbody {table.getRowModel().rows.map((row) ( Fragment key{row.id} {/* Normal row UI */} tr {row.getVisibleCells().map((cell) ( td key{cell.id} table.FlexRender cell{cell} / /td ))} /tr {/* If the row is expanded, render the expanded UI as a separate row with a single cell that spans the width of the table */} {row.getIsExpanded() ( tr {/* span however many columns the expanded data needs if it does not share the parent rows columns */} td colSpan{row.getAllCells().length} {/* Your custom UI goes here */} /td /tr )} /Fragment ))} /tbody要点展开的 UI 以独立的tr渲染在父行之后当展开内容不共享父行的列结构时用colSpan{row.getAllCells().length}跨整行宽度table.FlexRender是 Octane 表格的渲染出口详见 flex-render.md。从源码角度看row_getCanExpand的默认实现是options.getRowCanExpand?.(row) ?? ((options.enableExpanding ?? true) !!row.subRows.length)见 rowExpandingFeature.utils.ts即自定义getRowCanExpand拥有最高优先级未提供时只要enableExpanding默认true且行上有子行行即可展开。同理row_getIsExpanded也支持用options.getIsRowExpanded覆盖状态推导行为适合某些行强制展开/折叠的场景。四、展开状态管理外部 Atomv9 推荐与 v8 风格受控状态4.1 用外部 atom 拥有 expanded 状态推荐如果你需要在应用的其它部分读取展开状态v9 的推荐方式是把expanded状态切片交给外部 atom通过atoms选项注入。Atom 保留细粒度订阅展开值可以在任何组件中读取而不会迫使拥有表格的组件整体重渲染import { useCreateAtom, useSelector } from tanstack/octane-store const expandedAtom useCreateAtomExpandedState({}) // subscribe to the atom wherever you need the value const expanded useSelector(expandedAtom) const table useTable({ features, // other options... atoms: { expanded: expandedAtom, // expanding APIs now update expandedAtom }, })从实现看tanstack/octane-table的 useTable.tsrx 基于 TanStack Store 的 atom 构建表格实例表格状态与外部 atom 共享同一存储因此展开相关的 API如toggleExpanded会直接写入expandedAtom。4.2 v8 风格受控状态兼容迁移若更习惯经典写法state.expandedonExpandedChange模式仍然受支持。它适合简单集成或从 v8 迁移的代码但粒度不如外部 atom 细const [expanded, setExpanded] useStateExpandedState({}) const table useTable({ features, // other options... state: { expanded, }, onExpandedChange: setExpanded, })两种方式的取舍可进一步参考 table-state.md。另外onExpandedChange的默认实现是makeStateUpdater(expanded, table)见 rowExpandingFeature.ts即不传该选项时展开状态由表格内部 state 更新器接管。4.3 ExpandedState 类型语义type ExpandedState true | Recordstring, booleantrue所有行全部展开记录对象只有那些作为 key 存在且值为true的行 ID 处于展开状态。例如{ row1: true, row2: false }表示row1展开、row2不展开。表格依据该状态决定哪些行展开并显示其subRows。源码中table_getIsSomeRowsExpanded会把true当作存在展开行table_getIsAllRowsExpanded则会在expanded true时直接返回true以节省计算见 rowExpandingFeature.utils.ts。五、展开 UI 切换你需要自己添加切换按钮TanStack Table 不会自动为展开数据添加切换 UI需要你在每行 UI 中手动放置展开/折叠控件。最直接的做法是在列定义中放一个按钮const columns [ { accessorKey: name, header: Name, }, { accessorKey: age, header: Age, }, { header: Children, cell: ({ row }) { return row.getCanExpand() ? ( button onClick{row.getToggleExpandedHandler()} style{{ cursor: pointer }} {row.getIsExpanded() ? : } /button ) : ( ) }, }, ]实际项目中还可以用row.depth配合paddingLeft做出树形缩进效果。示例 main.tsrx 中即采用paddingLeft: \${row.depth * 2}rem来视觉化层级深度并在首列表头用table.getToggleAllRowsExpandedHandler() 提供全部展开/折叠按钮。六、展开 API 速查行级 API读取与切换单行展开状态row.getCanExpand() row.getIsExpanded() row.getIsAllParentsExpanded() row.getToggleExpandedHandler() row.toggleExpanded()row.getIsAllParentsExpanded()沿parentId链向上检查所有祖先是否都已展开当前行自身不参与判断源码中通过 while 循环逐级调用row_getIsExpanded实现row.getToggleExpandedHandler()返回一个事件处理函数行不可展开时为 no-oprow.toggleExpanded(expanded?)省略参数时按当前状态取反。表级 API读取与切换全局展开状态table.getCanSomeRowsExpand() table.getIsAllRowsExpanded() table.getIsSomeRowsExpanded() table.getExpandedDepth() table.getToggleAllRowsExpandedHandler() table.toggleAllRowsExpanded() table.resetExpanded()table.setExpanded(updater)直接更新展开状态updater可以是true、行 ID map或接收旧状态的函数table.resetExpanded()重置为initialState.expanded若配置了table.resetExpanded(true)则清空展开状态重置为空 map。从 rowExpandingFeature.utils.ts 的table_resetExpanded可以看到无参时克隆initialState.expanded传true时忽略初始状态直接置空值得注意的实现细节table.getCanSomeRowsExpand()基于分页前的flatRows判断这样即使可展开行不在当前页全局控制按钮也能正确反映表格的展开能力table.getExpandedDepth()通过按.切分行 ID 计算最大展开深度展开状态为true时扫描当前 row model 中所有可展开行否则扫描展开 map 的 key。七、展开与其他特性的协作7.1 与过滤协作filterFromLeafRows 与 maxLeafRowFilterDepth默认过滤从父行开始向下进行父行被过滤掉其所有子行也一并被排除。可通过filterFromLeafRows反转方向——从叶子子行向上过滤只要某个子行或孙行满足过滤条件其祖先行就会被保留。maxLeafRowFilterDepth则限制过滤考虑的子行最大深度。const features tableFeatures({ columnFilteringFeature, rowExpandingFeature, filteredRowModel: createFilteredRowModel(), expandedRowModel: createExpandedRowModel(), filterFns, }) //... const table useTable({ features, getSubRows: (row) row.subRows, filterFromLeafRows: true, // search through the expanded rows maxLeafRowFilterDepth: 1, // limit the depth of the expanded rows that are searched // other options... })注意这里的 row model 顺序filteredRowModel与expandedRowModel都注册进tableFeatures最终构成排序 → 过滤 → 展开 → 分页的标准行模型流水线示例 main.tsrx 同时注册了 sorted/filtered/expanded/paginated 四个 row model 工厂。7.2 与分页协作paginateExpandedRows默认展开行与表格其余行一起分页这意味着一个父行的展开子行可能跨越多个页面。若希望子行始终与父行同页渲染设置paginateExpandedRows: false副作用是单页渲染的行数会超过设定的 pageSizeconst table useTable({ features, // other options... paginateExpandedRows: false, })该选项默认值为true见 rowExpandingFeature.ts 的getDefaultTableOptions。配合 createExpandedRowModel.ts 的源码可知paginateExpandedRows: false且非手动分页时展开逻辑会提前返回把行展开交由分页模型处理从而保证父子同页。7.3 与行固定协作遵循行固定规则展开行与普通行的固定pinning行为完全一致可以固定到表格顶部或底部细节参考 row-pinning.md。7.4 与排序协作默认情况下展开行与表格其余行一起参与排序即按父行排序结果展开其子行不单独对子行排序。八、自动重置展开状态autoResetExpanded 与 autoResetAll如果你同时使用分组grouping特性每当分组行模型因data或分组状态变化而重算时expanded状态会自动重置。当manualExpanding为true时该默认行为自动关闭你也可以显式给autoResetExpanded赋布尔值覆盖。此外全局autoResetAll选项可一次性关闭或开启所有自动重置行为。最常见的设置autoResetExpanded: false的场景是边看表边编辑数据例如行内单元格编辑每次编辑都会更新data进而触发行模型重算默认行为会折叠用户已展开的行。如果同时使用分页建议搭配autoResetPageIndex: false以保留当前页const table useTable({ features, // other options... autoResetExpanded: false, // keep expanded state when data changes // autoResetAll: false, // or turn off all auto resets at once })实现层面rowExpandingFeature.utils.ts 的table_autoResetExpanded按autoResetAll ?? autoResetExpanded ?? !manualExpanding的优先级决定是否安排重置未显式配置时客户端展开manualExpanding: false会默认自动重置而手动展开默认不重置。仓库测试 autoReset.test.ts 与 rowExpandingFeature.test.ts 覆盖了这类自动重置与展开行为组合的验证。九、手动展开服务端场景如果展开逻辑在服务端完成可设置manualExpanding: true。此时getExpandedRowModel不再参与展开你需要自行在数据模型层面完成展开比如直接让服务端返回已经展开好、带有子行的扁平数据const features tableFeatures({ rowExpandingFeature }) // no expandedRowModel for manual expanding const table useTable({ features, // other options... manualExpanding: true, })手动展开模式下不需要注册expandedRowModel工厂。展开 APIgetIsExpanded、toggleExpanded等依然可用只是数据的展开形态由你全权掌控同时如上一节所述manualExpanding: true也会默认关闭 expanded 的自动重置。十、完整示例与下一步想直接看可运行的实现仓库提供了两个入口Octane 官方示例examples/octane/expanding集成了子行展开、过滤含between/includesString/inNumberRange自定义 filterFn、分页、排序、行选择与debugTable调试面板支持 10k 行压力测试main.tsrx中几乎每一行展开相关选项都有注释化的配置演示核心实现与测试展开 feature 实现在 packages/table-core/src/features/row-expanding/rowExpandingFeature.ts 与 rowExpandingFeature.utils.ts展开行模型在 createExpandedRowModel.ts对应测试位于 rowExpandingFeature.test.ts 与 createExpandedRowModel.test.ts可作为深入阅读与行为验证的参考。在此基础上可以继续阅读 grouping.md分组与展开常组合使用、aggregation.md展开聚合行以及 pagination.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点击查看免费下载相关推荐TanStack Alpine Table 行展开Expanding功能实战指南子行、详情面板与状态管理TanStack Alpine Table 行展开Expanding功能实战指南子行、详情面板与状态管理 TanStack Alpine Table 的前端UI组件Bubble Navigation实战构建现代化电商App导航系统的终极指南Bubble Navigation实战构建现代化电商App导航系统的终极指南 在移动应用开发中 Bubble Navigation 是打造现代化电商App导前端UI组件TanStack Alpine Table 行固定Row Pinning完全指南从状态管理到模板渲染TanStack Alpine Table 行固定Row Pinning完全指南从状态管理到模板渲染 行固定Row Pinning允许你把选中的行固定前端UI组件上一篇【限时免费】 4.10热门项目推荐unlock-deepseek - 解密大语言模型核心技术下一篇BlenderMCP 实战指南把 AI 接进 Blender 的完整配置流程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考