TanStack Table Preact 适配器核心类型解析PreactTable 接口与 useTable 返回值全指南【免费下载链接】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 Table Preact 适配器tanstack/preact-table的核心类型别名PreactTableTFeatures, TData, TSelected展开它正是useTableHook 返回的表格实例类型也是 Preact 中一切表格渲染、状态订阅与性能优化的入口。读完本文你将掌握PreactTable的完整成员state、store、Subscribe、FlexRender各自的作用与适用场景学会用选择器selector裁剪渲染范围、用Subscribe做细粒度订阅、用FlexRender统一渲染表头/单元格/表尾并理解这些 API 在源码层面的实现原理。PreactTable 是什么useTable 的返回值类型在 Preact 中使用 TanStack Table 时核心 API 只有一个 Hookimport { useTable } from tanstack/preact-table const table useTable(tableOptions, selector?)而useTable的返回类型就是本篇文章的主角。其类型定义位于 useTable.ts:27-95完整声明如下type PreactTableTFeatures, TData, TSelected OmitTableTFeatures, TData, store object;它由两部分合成OmitTableTFeatures, TData, store继承自框架无关的核心表类型Table来自tanstack/table-core但移除了store属性的原始声明以便重新定义其类型 object叠加 Preact 适配器专属的四个成员——state、store重声明、Subscribe、FlexRender。也就是说PreactTable在保留了表核心全部能力列定义、数据、排序、过滤、分组、分页等的同时额外提供了 Preact 环境下的响应式状态与渲染组件。从源码可以看到 useTable.ts:132-157 中Hook 内部通过constructTableTFeatures, TData(...)构造核心表实例并把preactReactivity()注入features随后为实例挂上Subscribe与FlexRender两个组件级成员最终返回一个符合该类型约束的对象。三个类型参数的含义type PreactTable TFeatures extends TableFeatures, TData extends RowData, TSelected TableStateTFeatures, 参数约束默认值作用TFeaturesextends TableFeatures无表格启用的功能特性集合排序、分页、行选择、分组、列固定等由传给useTable的features决定驱动TableState的形状与列/单元格/表头的类型上下文TDataextends RowData无行数据的类型如Person、User贯穿列定义、单元格取值与状态结构TSelected—TableStateTFeaturesuseTable第二个参数selector投影出的状态类型不传 selector 时默认为完整表状态TSelected的默认值很关键不提供 selector 时table.state就是整个TableState提供了 selector 后table.state会被收窄为投影结果类型。这在 useTable.ts:94 的成员声明中有明确注释table.state是被 selector 函数筛选后的状态其结构可能与完整表状态不一致。成员一state —— 供渲染读取的选择后状态readonly state: ReadonlyTSelected;state是 Preact 渲染中最推荐直接读取的状态来源。它由useTable的第二个参数可选的 selector 函数从table.store中投影而来并且在内部经过浅比较shallow compare只有投影结果变化时才触发 Preact 重渲染。从 useTable.ts:173-190 的实现可以看到完整的构建链路const renderSnapshot rootSource.get() const state useSelector(rootSource, selector, { compare: shallow }) useIsomorphicLayoutEffect(() { rootSource.markCommitted(renderSnapshot) table_publishExternalState(coreTable, controlledState ?? null, shallow) })rootSource是一个渲染阶段数据源render-phase source由createRenderPhaseSource(tableInstance.store, shallow)创建见 useTable.ts:153-156它只对真正提交commit的宿主渲染推进通知基线避免被废弃渲染中的读取污染useSelector(rootSource, selector, { compare: shallow })以浅比较订阅投影结果useIsomorphicLayoutEffect中通过markCommitted提交渲染快照并借助table_publishExternalState把受控状态发布给响应式订阅者。使用示例const table useTable( { features, columns, data }, (state) ({ pagination: state.pagination }), ) // table.state 的类型被收窄为 { pagination: ... } table.state.pagination配套的单元测试adapterReactivity.test.tsx验证了带 selector 时只有被选中状态变化才触发重渲染未选中的状态变更不会导致组件重新执行。成员二store已废弃—— 为什么不再推荐readonly store: TableTFeatures, TData[store];store属性是 TanStack Store 的原始 store 实例类型层面被标记为deprecated见 useTable.ts:32-39。官方给出的迁移指引非常明确按场景推荐三种替代方案场景推荐 API渲染过程中读取状态table.state获取某个状态切片的快照table.atoms.slice.get()如table.atoms.rowSelection.get()显式订阅状态变化table.Subscribe组件或useSelector(table.store, selector)弃用的原因在注释中说明得很清楚table.store.state只是一个当前值的快照在渲染代码中直接读取它很容易被误用比如拿不到订阅关系、无法保证渲染时机的正确性。因此在 Preact 适配器中这一属性被保留仅用于兼容旧代码新代码应优先使用state与Subscribe。成员三FlexRender —— 统一渲染表头、单元格与表尾FlexRender: TValue(props) ComponentChildren;FlexRender是 PreactTable 上暴露的一个 Preact 组件用于渲染表头header、单元格cell或表尾footer官方文档明确建议用它替代手动调用flexRender函数。它的 props 类型为FlexRenderPropsTFeatures, TData, TValue定义在 FlexRenderProps.md是一个三选一的互斥联合类型——每次只能传cell、header、footer三者之一type FlexRenderPropsTFeatures, TData, TValue | { cell: CellTFeatures, TData, TValue; header?: never; footer?: never } | { header: HeaderTFeatures, TData, TValue; cell?: never; footer?: never } | { footer: HeaderTFeatures, TData, TValue; cell?: never; header?: never }其中TValue extends CellData CellData是泛型参数默认值为CellData。基本用法FlexRender cell{cell} / FlexRender header{header} / FlexRender footer{footer} /组件内部实现见 FlexRender.tsx:101-150会把传入的cell/header/footer转换为对应的列定义渲染函数并注入上下文等价于手写flexRender(cell.column.columnDef.cell, cell.getContext()) flexRender(header.column.columnDef.header, header.getContext()) flexRender(footer.column.columnDef.footer, footer.getContext())源码级细节分组聚合的特殊处理在 FlexRender.tsx:106-137 中cell分支额外处理了列分组column-grouping特性注册后的三种特殊单元格模式聚合单元格aggregated优先渲染columnDef.aggregatedCell未定义时回退到columnDef.cell占位单元格placeholder分组内重复出现的值直接返回null不渲染分组单元格grouped作为分组头单元格交给columnDef.cell渲染消费方通常会在调用FlexRender前先用cell.getIsGrouped()自行分支定制。这些方法getIsAggregated、getIsPlaceholder在分组特性未注册时在类型层面不存在源码通过可选链与类型断言保证其安全性——这正是 flex-render.ts 中flexRender基础工具识别类组件、函数组件与preact.memo/react.memo等 exotic 组件并分别渲染之上的便利封装。成员四Subscribe —— 细粒度状态订阅的 HOCSubscribe是一个高阶组件Higher Order Component允许你在组件树的任意位置订阅表格状态并且只让订阅的部分重渲染。它有三种调用签名overloads源码注释useTable.ts:63-66说明重载按先 source 后 store的顺序排列以便 JSX 的上下文类型推断在两种模式下都能正常工作。Subscribe: { TSourceValue (props): ComponentChildren; TSourceValue, TSubSelected (props): ComponentChildren; TSubSelected (props): ComponentChildren; };重载一订阅 source无 selector恒等投影TSourceValue(props): ComponentChildren不传selectorchildren 直接接收到source的完整值TSourceValue恒等投影。把无 selector 的 source 模式单独拆成一个签名是为了让 children 的类型参数能直接推断出TSourceValuetable.Subscribe source{table.atoms.rowSelection} {(rowSelection) div已选中 {Object.keys(rowSelection).length} 行/div} /table.Subscribe重载二订阅 source selector投影订阅TSourceValue, TSubSelected(props): ComponentChildren对某个原子atom或 store 先做投影再订阅table.Subscribe source{table.atoms.rowSelection} selector{(s) s?.[row.id]} {(selected) input checked{!!selected} typecheckbox /} /table.Subscribesource的类型是SubscribeSourceTValue定义在 SubscribeSource.mdtype SubscribeSourceTValue | AtomTValue | ReadonlyAtomTValue | StoreTValue | ReadonlyStoreTValue;即可以是任意可读原子或 store典型用法是table.atoms.rowSelection、table.atoms.pagination等状态切片原子或table.optionsStore。重载三订阅整个 storeselector 必填TSubSelected(props: OmitSubscribePropsWithStoreTFeatures, TSubSelected, source)store 模式不需要传source组件会自动回填table.store但selector是必填的。这一点在 SubscribePropsWithStore.md 中有明确说明store 模式下强制要求 selector是为了防止你无意中订阅整个 store 而没有显式投影。table.Subscribe selector{(state) ({ rowSelection: state.rowSelection })} {({ rowSelection }) ( tr key{row.id}.../tr )} /table.Subscribe实现原理Subscribe的独立实现位于 Subscribe.ts:98-125内部调用useSelector(props.source, props.selector, { compare: shallow })完成浅比较订阅然后把投影后的selected传入children。如果children是函数则调用它并传入选中值否则直接渲染静态 children。useTable返回的table.Subscribe则在 useTable.ts:140-145 中被包装当未传source时自动回填tableInstance.store从而同时支持三种重载。而Subscribe所需的响应式绑定由 reactivity.ts 提供preactReactivity()基于tanstack/preact-store的createAtom与batch配合tanstack/table-core的渲染阶段响应式预设renderPhaseReactivity构建——所有原子共享同一个 store 实例用户提供的外部原子也能无缝混入。性能最佳实践三种读取方式的选择综合PreactTable各成员的特性在实际组件中应按下述优先级选择状态读取方式顶层渲染在调用useTable的组件中用useTable(options, selector)的第二个参数收窄table.state只订阅本次渲染真正关心的状态切片深层节点把需要响应状态变化的 JSX 拆成子组件用table.Subscribesource 或 store 模式做定点订阅避免整个表格树因局部状态如某一行是否被选中而整体重渲染渲染外逻辑需要即时读取但不需要订阅时用table.atoms.slice.get()获取切片快照旧代码避免直接使用已废弃的table.store.state。配套测试 adapterReactivity.test.tsx 覆盖了选择器投影、外部原子注入、渲染阶段提交与受控状态发布等关键行为是理解PreactTable各成员协作方式的最佳参考资料。小结PreactTable是useTable返回值的类型蓝本它以框架无关的Table为核心叠加了四个 Preact 专属能力state渲染期状态、Subscribe细粒度订阅、FlexRender统一渲染组件以及已废弃的store。理解这一类型别名及其源码实现useTable.ts、FlexRender.tsx、Subscribe.ts是写出高性能、类型安全的 Preact 数据表格的前提。【免费下载链接】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创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考