TanStack Table React 的 SubscribeProps 类型别名:用三路联合类型实现细粒度表状态订阅
TanStack Table React 的 SubscribeProps 类型别名用三路联合类型实现细粒度表状态订阅【免费下载链接】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 v9本仓库对应packages/react-table中状态管理全面转向 TanStack Store 的原子化模型而SubscribeProps正是驱动这一模型的核心类型契约。它定义了Subscribe组件与table.Subscribe方法接受的全部 props 形态既可以订阅整个table.store也可以只订阅某个状态原子如table.atoms.rowSelection并且通过选择器selector投影出最小化的渲染数据。读完本文你将完整掌握SubscribeProps的三个联合分支、SubscribeSource的取值来源以及如何在表格中落地只在需要的地方重渲染的精细订阅方案。SubscribeProps 的类型签名与泛型参数SubscribeProps定义在 react-table/src/Subscribe.ts是三种订阅形态的联合类型union typeexport type SubscribeProps TFeatures extends TableFeatures, TSelected unknown, TSourceValue unknown, | SubscribePropsWithStoreTFeatures, TSelected | SubscribePropsWithSourceIdentityTSourceValue | SubscribePropsWithSourceWithSelectorTSourceValue, TSelected三个泛型参数各有明确职责泛型参数约束 / 默认值含义TFeaturesextends TableFeatures表特性集合类型。由tableFeatures({...})注册的特性决定哪些状态切片可用例如注册了rowSelectionFeature才有table.atoms.rowSelectionTSelected默认unknown选择器投影后的值类型即children渲染函数实际接收的参数类型TSourceValue默认unknown订阅源atom 或 store的值类型例如RowSelectionState这里采用联合类型而非单一对象类型是为了在类型层面强制约束三种互斥的订阅模式store 模式必须显式提供 selectorsource 模式要么不提供 selector恒等投影要么提供 selector投影子集。这样 TypeScript 可以在编译期拦截订阅了整个 store 却没有投影这类容易引发大面积重渲染的误用。分支一SubscribePropsWithStore —— 订阅完整表状态强制 selectorSubscribePropsWithStore定义在 react-table/src/Subscribe.ts对应文档中Subscribe totable.store(full table state)的语义其结构为export type SubscribePropsWithStore TFeatures extends TableFeatures, TSelected, { source: SubscribeSourceTableStateTFeatures selector: (state: TableStateTFeatures) TSelected children: ((state: TSelected) ReactNode) | ReactNode }该分支的三个属性要点source类型为SubscribeSourceTableStateTFeatures即订阅源是承载完整TableState的 store通常是table.store。selector必填。接收完整的TableState返回投影值TSelected。源码注释明确说明Required in store mode so you never accidentally subscribe to the whole store without an explicit projection——在 store 模式下 selector 是强制的防止你无意中订阅整个 store 而不做任何投影。同时注释指出其比较策略为浅比较shallow compareRe-renders when the selected value changes (shallow compare)。children可以是接收TSelected的渲染函数render prop也可以是一个静态ReactNode。典型用法是同时投影多个状态切片聚合到一个渲染块中Subscribe source{table.store} selector{(state) ({ columnFilters: state.columnFilters, globalFilter: state.globalFilter, rowSelection: state.rowSelection, })} {() ( IndeterminateCheckbox checked{table.getIsAllRowsSelected()} indeterminate{table.getIsSomeRowsSelected()} onChange{table.getToggleAllRowsSelectedHandler()} / )} /Subscribe该示例取自 basic-subscribe 示例表头全选复选框依赖过滤与行选择三类状态因此用 store 模式一次性投影。分支二SubscribePropsWithSourceIdentity —— 订阅源的完整值省略 selectorSubscribePropsWithSourceIdentity定义在 react-table/src/Subscribe.ts对应文档中Subscribe to the full value of a source (e.g.table.atoms.rowSelectionortable.optionsStore)的语义export type SubscribePropsWithSourceIdentityTSourceValue { source: SubscribeSourceTSourceValue selector?: undefined children: ((state: TSourceValue) ReactNode) | ReactNode }关键语义selector是可选属性且类型被限定为undefined。源码注释解释Omittingselectoris equivalent to the identity selector — children receiveTSourceValue。也就是说省略 selector 等价于恒等选择器children直接收到订阅源的完整值。之所以把selector声明为optional selector: undefined而不是直接不声明该属性是为了让联合类型的分支判别更精确——TypeScript 可以据此区分恒等投影与带 selector 的投影两种形态从而获得更好的类型推断。children是渲染函数时直接接收TSourceValue。最典型的场景是只关心某一个状态切片atom// 订阅行选择原子children 直接拿到完整的 RowSelectionState table.Subscribe source{table.atoms.rowSelection} {(rowSelection) ( divSelected rows: {Object.keys(rowSelection).length}/div )} /table.Subscribe分支三SubscribePropsWithSourceWithSelector —— 对订阅源做投影SubscribePropsWithSourceWithSelector定义在 react-table/src/Subscribe.ts对应文档中Subscribe to a projected value from a source (atom or store)的语义export type SubscribePropsWithSourceWithSelectorTSourceValue, TSelected { source: SubscribeSourceTSourceValue selector: (state: TSourceValue) TSelected children: ((state: TSelected) ReactNode) | ReactNode }与恒等分支的差别在于selector为必填接收源值TSourceValue返回投影后的TSelectedchildren渲染函数收到的不是源值而是投影值TSelected。这是把订阅范围压缩到最小渲染面的核心手段。例如行选择场景中整张表只关心当前这一行是否被选中而非整个选择状态table.Subscribe source{table.atoms.rowSelection} selector{(rowSelection) rowSelection[row.id]} {(isRowSelected) ( div classNamecolumn-toggle-row IndeterminateCheckbox checked{!!isRowSelected} disabled{!row.getCanSelect()} indeterminate{row.getIsSomeSelected()} onChange{row.getToggleSelectedHandler()} / /div )} /table.Subscribe该写法来自 basic-subscribe 示例selector把整份RowSelectionState投影成当前行的布尔值因此切换某一行时只有该行复选框所在的订阅岛会重渲染其他行不受影响。SubscribeSource订阅源的统一抽象三种分支共享同一个source属性其类型SubscribeSource定义在同文件的 react-table/src/Subscribe.tsexport type SubscribeSourceTValue | AtomTValue | ReadonlyAtomTValue | StoreTValue | ReadonlyStoreTValue它囊括了 TanStack Store 的四种可订阅对象即tanstack/react-store导出的Atom、ReadonlyAtom、Store、ReadonlyStore。这一抽象使得Subscribe既可以订阅单一状态切片atom也可以订阅完整的扁平化状态仓库store且不区分读写形态——只要具备可订阅协议即可。在 TanStack Table v9 的状态模型中table.baseAtoms是由初始状态解析出的内部可写原子table.atoms是按注册状态切片暴露的只读派生原子如table.atoms.rowSelection、table.atoms.paginationtable.store是把所有已注册的table.atoms聚合而成的只读扁平化 Store。详见 Table State 指南。正因为 atom 与 store 共享同一套选择协议Subscribe组件内部才能用同一个useSelector处理所有分支。源码实现useSelector 浅比较SubscribeProps最终由Subscribe组件消费。其完整实现位于 react-table/src/Subscribe.tsexport function Subscribe TFeatures extends TableFeatures, TSelected, TSourceValue, ( props: SubscribePropsTFeatures, TSelected, TSourceValue, ): ReturnTypeFunctionComponent { const selected useSelector( // Atom and store share the same selection protocol; union args need a widen for TS. props.source, props.selector as Parameterstypeof useSelector[1], { compare: shallow, }, ) as TSelected return typeof props.children function ? (props.children as (state: TSelected) ReactNode)(selected) : props.children }从实现可以确认三个关键机制底层复用useSelectorSubscribe只是把tanstack/react-store的useSelector封装成 JSX 组件形态因此它天然遵循 TanStack Store 的订阅与变更检测协议。比较策略为shallow通过compare: shallow做浅比较只有投影结果变化时才会触发重渲染这正是行未变化就不重渲染的底层保障。children 双形态函数形态render prop会收到投影值并作为渲染结果非函数形态则直接渲染静态ReactNode适合订阅副作用、不订阅渲染的场景。Subscribe对外提供了三组重载overload分别对应三个联合分支见 react-table/src/Subscribe.ts。两种使用入口table.Subscribe 与独立 Subscribe 组件SubscribeProps实际被两个 API 共享table.Subscribe实例方法推荐useTable返回的 React 面 table 上的订阅方法。源码注释指出Fortable.SubscribefromuseTable, prefer that API — it uses overloads so JSX contextual typing works. 即实例方法使用重载JSX 上下文类型推断更准确写法上可省略source默认订阅table.storetable.Subscribe selector{(state) ({ rowSelection: state.rowSelection })} {({ rowSelection }) ( divSelected rows: {Object.keys(rowSelection).length}/div )} /table.Subscribe独立Subscribe组件在 cell / header 渲染上下文等拿不到 React 面 table 的地方使用。此时table是 core Table 而非useTable的返回值需要显式传入sourcetable.store或table.atoms中的某一项组件本身使用联合 props 类型Subscribe source{table.atoms.rowSelection} {(rowSelection) div.../div} /Subscribe两条入口的 props 结构完全由SubscribeProps定义差异仅在于source是否可省略。相关说明见 React Compiler 指南 与 Table State 指南。实战模式三类订阅的选型建议在 basic-subscribe 示例 中三种模式被组合用于同一张表表头全选store 模式 多切片投影依赖列过滤、全局过滤、行选择三类状态。行复选框atom 投影模式selector只取当前行选择值。全局过滤输入框订阅table.atoms.globalFilter把表主体与过滤控件解耦。选型建议可归纳为场景推荐模式原因需要聚合多个状态切片到一个渲染块SubscribePropsWithStore一次投影浅比较控制重渲染只关心某一个完整切片SubscribePropsWithSourceIdentity省略 selector 即恒等投影代码最简关心某一切片的子集如单行、单列SubscribePropsWithSourceWithSelector重渲染面最小化性能最优需要说明的是SubscribeProps属于性能优化利器而非默认选项。Table State 指南 明确建议通常在useTable默认 selector 造成可见性能问题时再引入table.Subscribe。默认情况下useTable的 selector 会选择全部已注册状态组件随任何状态变化重渲染将 selector 收窄如() null后配合table.Subscribe把响应式读取下沉到真正需要的地方即可实现父组件不因表格状态重渲染、子组件按需重渲染。与 React Compiler 的配合SubscribeProps也是 Table v9 兼容 React Compiler 的关键一环。在嵌套组件只接收稳定的table、row、cell等对象作为 props 时编译器可能因 props 未变而跳过组件渲染从而漏掉状态变化。正确的做法是把Subscribe/table.Subscribe放在真正读取状态的组件内部订阅最小的状态片段如rowSelection[row.id]并直接从投影值渲染详见 React Compiler 指南。由于useSelector是编译器能够识别的响应式依赖这种方式可以完全规避React.memo结合方法 getter如cell.getIsSelected()时状态依赖被隐藏的问题。相关性能对比与测试依据可参考该指南中维护的基准说明。小结SubscribeProps是 TanStack Table React 精细订阅体系的类型基石它以三个联合分支完整刻画了订阅 store / 订阅源 / 订阅源投影三种形态配合SubscribeSource对 atom 与 store 的统一抽象让开发者可以在 React 树中精确控制重渲染边界。理解这一类型别名是掌握table.Subscribe、独立Subscribe组件乃至 Table v9 原子化状态模型的前提建议进一步阅读 Table State 指南 了解状态读取全貌并结合 basic-subscribe 示例 与 kitchen-sink 示例 的完整实现进行验证。【免费下载链接】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),仅供参考