TanStack Table v9(Octane 适配器)`SubscribePropsWithSourceWithSelector` 接口详解:从原子或 Store 源订阅投影状态
2026/9/21 15:25:13 网站建设 项目流程
  • 前端
  • 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
点击查看免费下载

导读

SubscribePropsWithSourceWithSelector<TSourceValue, TSelected>@tanstack/octane-tableSubscribe组件的一种 props 形态,用于让 Octane 组件从某个状态源(TanStack Store 的原子AtomStore)订阅一个投影后的值selector接收源值,而children渲染函数接收投影结果TSelected。它支撑了 Octane 表格"只让树中需要刷新的节点精确重渲染"的细粒度订阅模型,是优化大表格性能、实现行选择/分页/过滤等局部响应式 UI 的基础。阅读本文后,你将掌握该接口的完整属性签名、与"恒等订阅""整表订阅"两种形态的区别,以及它在源码层和真实示例中的用法。

该接口定义于 packages/octane-table/src/types.ts,是SubscribeProps联合类型三种形态之一,本文围绕它展开,并结合仓库源码与示例进行纵深讲解。

接口定位:Subscribe 三种 props 形态中的"投影订阅"

在 Octane 适配器中,Subscribe组件(以及useTable返回实例上的table.Subscribe)可以订阅三类状态源,分别对应三种 props 形态(见 types.ts):

Props 形态source 取值selectorchildren 收到典型场景
SubscribePropsWithStore<TFeatures, TSelected>table.store(整表扁平 Store)必填,从TableState投影TSelected只关心分页、过滤等若干状态切片
SubscribePropsWithSourceIdentity<TSourceValue>单个 Atom 或 Store省略(恒等投影)完整的TSourceValue订阅整个行选择 Atom
SubscribePropsWithSourceWithSelector<TSourceValue, TSelected>单个 Atom 或 Store必填投影结果TSelected从行选择 Atom 中投影单行选中状态

三个接口的完整定义与源码注释见 types.ts,其中本接口的关键注释为:

Subscribe to a projected value from a source (atom or store). The selector receives the source value; children receive the projectedTSelected.

SubscribeProps本身是这三种形态的联合(types.ts),Subscribe组件的导出类型SubscribeComponent则用重载的可调用类型来表达同一约束(types.ts),以保证 JSX 调用点处selector回调能获得正确的上下文类型推断。

类型参数:TSourceValueTSelected

该接口声明了TSourceValueTSelected两个泛型参数:

  • TSourceValue:状态源所承载的值的类型,即sourceselector输入参数的类型。它由source传入的 Atom/Store 推断得出。
  • TSelectedselector的返回类型,也就是children渲染函数实际收到的投影值的类型。它由selector函数的返回类型推断得出。

值得强调的是重载顺序的用意:在 SubscribeComponent 中,source 相关重载排在 store 重载之前,这样TSourceValue能优先从source推断;而"无selector的恒等重载"被单独拆开(SubscribePropsWithSourceIdentity),使得省略selectorchildren收到的是TSourceValue而非退化为unknown。若把selector做成可选参数放在同一个重载上,TSelected会默认成unknown,丢失推断精度——这是该接口被拆成两个独立形态的根本原因。

三个属性逐一拆解

接口的完整属性签名如下(对应 types.ts):

export interface SubscribePropsWithSourceWithSelector<TSourceValue, TSelected> { source: SubscribeSource<TSourceValue> selector: (state: TSourceValue) => TSelected children: ((state: TSelected) => OctaneNode) | SubscribeStaticChild }

source: SubscribeSource<TSourceValue>

订阅的目标状态源,其类型定义于 types.ts:

export type SubscribeSource<TValue> = Atom<TValue> | ReadonlyAtom<TValue> | Store<TValue> | ReadonlyStore<TValue>

也就是说,可读可写的Atom、只读的ReadonlyAtom、以及Store/ReadonlyStore均可以作为source。在表格场景中,最常用的取值是:

  • table.atoms.rowSelectiontable.atoms.globalFiltertable.atoms.paginationtable.atoms.columnFilters按状态切片暴露的只读派生 Atom
  • table.store(整表扁平 Store,但本接口更鼓励订阅单一 Atom/Store 做局部投影);
  • 通过atoms选项传入的外部 Atom(例如useCreateAtom<RowSelectionState>({})创建的实例),此时source可以是在表格作用域之外独立存在的共享原子。

selector: (state: TSourceValue) => TSelected

投影函数。它接收source的完整值,返回投影后的TSelectedSubscribe的实现会把它传给 TanStack Store 的useSelector,因此:

  • 只有投影结果发生变化时,订阅者才会重渲染
  • 比较采用浅比较(shallow compare),因此对象字面量形式的 selector 结果(如{ rowSelection: state.rowSelection })在值未变时不会触发多余的重渲染。

这一点在实现 Subscribe.tsrx 中有直接印证:

const selected = useSelector( props.source as Parameters<typeof useSelector>[0], props.selector as Parameters<typeof useSelector>[1], { compare: shallow }, )

shallow比较的引入正是为了与上游保持一致:对象字面量选择器不会在每次状态通知时都触发重渲染。

children: ((state: TSelected) => OctaneNode) | SubscribeStaticChild

children 有两种形态:

  1. 渲染函数(render prop)(state: TSelected) => OctaneNode,接收投影后的TSelected并返回 Octane 节点。这是本接口的核心用法——children拿到的不是原始源值,而是selector投影后的值。
  2. 静态子节点SubscribeStaticChild(定义于 types.ts),可以是ElementDescriptor、字符串、数字、布尔值、null/undefined或只读数组。它保持具体类型而非直接使用OctaneNode(当前为unknown),是为了让函数形式的 children 仍能获得上下文类型推断。

实现中(Subscribe.tsrx)对 children 做了区分处理:函数则用投影后的selected调用之,否则原样渲染静态子节点。

底层机制:useSelector+shallow的订阅协议

Subscribe组件被作者用.tsrx文件编写(见 Subscribe.tsrx 的头部注释),因为它内部调用了 slot 键控的 hookuseSelector,编译器会为它在调用点分配渲染 slot。Atom 与 Store 共用同一套选择协议:

  1. 组件首次渲染时,useSelectorsource执行selector,得到TSelected
  2. 后续当该 Atom/Store 状态变化时,TanStack Store 重新计算selector,并用shallow与上一次结果比较;
  3. 若投影结果不同,则仅重渲染该Subscribe节点及其子树——即"重渲染精确落在需要它的树节点上"。

值得注意的实现细节是:SubscribeImpl的函数体接收的是联合类型SubscribeProps,但导出的绑定被重新标注为重载可调用类型SubscribeComponent(Subscribe.tsrx)。这是因为.tsrx@{ … }函数体无法携带前置重载签名(TS2384 限制),而 JSX 调用点需要的是重载解析能力,可调用类型恰好提供这一点。调用点仍然获得按重载的逐项推断,只有这一个函数体内部看到联合类型。

table.Subscribe与独立Subscribe的差异

useTable返回的OctaneTable实例上挂载了预绑定好的table.Subscribe(useTable.tsrx):

tableInstance.Subscribe = ((props: any) => ( <Subscribe {...props} source={props.source ?? tableInstance.store} /> )) as OctaneTable<TFeatures, TData, TSelected>['Subscribe']

关键行为:

  • 不传source时,table.Subscribe自动以table.store作为源,此时等价于SubscribePropsWithStore形态,必须提供 selector
  • source(单个 Atom/Store)时,就落到本文所讲的SubscribePropsWithSourceWithSelector(带 selector)或SubscribePropsWithSourceIdentity(不带 selector)形态。

而独立导出的Subscribe(从@tanstack/octane-table引入,见 index.ts 的export * from './Subscribe.tsrx')没有预绑定的源,必须在source中显式传入table.store或某个 Atom。

useTable本身还通过第二个参数(可选 selector)将投影值暴露在table.state上(useTable.tsrx),并借助createRenderPhaseSource+ 布局副作用实现"渲染阶段暂存、提交后才发布"的 commit 语义——但那是整表级别的订阅,与本接口的"树中局部订阅"互补。关于两种读取策略(直接读快照 vs. 订阅式读取)的取舍,可进一步参考 docs/framework/octane/guide/table-state.md。

实战示例:单行选择投影与行模型局部订阅

仓库中的 examples/octane/basic-subscribe/src/main.tsrx 完整演示了本接口的两种典型用法。

用法一:从行选择 Atom 投影单行状态

function RowCheckbox({ row, table }: { row: Row<typeof features, Person>; table: TableInstance }) @{ // 只订阅该行的选中值,切换一行时仅重渲染该行的复选框 <table.Subscribe source={table.atoms.rowSelection} // 只订阅 row selection atom selector={(rowSelection) => Boolean(rowSelection[row.id])} // 只在该行选择变化时重渲染 > {(selected: boolean) => <IndeterminateCheckbox ariaLabel={`Select row ${row.id}`} checked={selected} disabled={!row.getCanSelect()} indeterminate={row.getIsSomeSelected()} onChange={row.getToggleSelectedHandler()} />} </table.Subscribe> }

这正是SubscribePropsWithSourceWithSelector的教科书级场景:sourcetable.atoms.rowSelectionReadonlyAtom<RowSelectionState>),selector把整个行选择状态投影为Boolean(rowSelection[row.id])的布尔值,children渲染函数收到的selected就是投影结果。当用户切换一行时,只有该行复选框的订阅节点重渲染,而不是整个表格。

用法二:从 Store 投影分页状态

<table.Subscribe selector={(state) => state.pagination}> {(pagination: PaginationState) => <div className="controls"><Subscribe source={table.store} selector={(state) => state}> {(state: TableState<typeof features>) => <pre><Subscribe source={externalSelection} selector={(selection) => Boolean(selection['1'])} children={(selectedValue) => { const typed: boolean = selectedValue return <span>{String(typed)}</span> }} />

这里externalSelectioncreateAtom<RowSelectionState>({})创建的外部 Atom,TSourceValue推断为RowSelectionStateselector返回boolean,于是children收到的selectedValue被断言为boolean且通过编译。这份测试同时覆盖了"无 selector 的恒等订阅"(children 收到RowSelectionState)与"带 selector 的投影订阅"两种重载,从类型系统层面保证了本接口的推断契约。

使用建议与注意事项

  1. 先默认、再优化:如 docs/framework/octane/guide/table-state.md 所建议,useTable默认会选中全部注册状态,日常开发先使用默认行为;只有当大表格出现可感知的性能问题时,再改用table.Subscribe配合本接口做局部订阅。
  2. 理解状态依赖再订阅basic-subscribe示例的注释提醒,很难知道该订阅什么,除非你理解内部 API 的每个状态依赖。务必测试并验证订阅确实在你预期的时机重渲染。
  3. selector 返回稳定引用:由于订阅用shallow比较结果,selector 应避免每次返回全新的不等价对象,否则可能引发多余重渲染。
  4. 外部 Atom 是推荐的控制方式:在 v9 中,需要与应用共享的状态(如服务端数据获取中的分页、排序、过滤)推荐通过atoms选项传入外部 Atom,再用SubscribePropsWithSourceWithSelector在任意需要的位置精确订阅。

小结

SubscribePropsWithSourceWithSelector<TSourceValue, TSelected>是 Octane 表格细粒度响应式模型的基石之一:source圈定状态源,selector把源值投影为 UI 真正需要的形状,children只接收投影结果。它与SubscribePropsWithSourceIdentity(恒等订阅)、SubscribePropsWithStore(整表订阅)共同构成Subscribe/table.Subscribe的完整 props 契约,其实现建立在 TanStack Store 的useSelector+shallow选择协议之上,类型推断则由重载的可调用类型保证。需要深入源码时,可依次阅读 packages/octane-table/src/types.ts、packages/octane-table/src/Subscribe.tsrx、packages/octane-table/src/useTable.tsrx,并结合 examples/octane/basic-subscribe/src/main.tsrx 与 docs/framework/octane/guide/table-state.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
点击查看免费下载
上一篇:Nerfies实战指南:如何训练你自己的可形变3D模型
下一篇:Sora2API高级功能深度解析:视频Remix与分镜功能的实现原理

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询