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

资讯详情

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

TanStack Table Ember 集成核心:深入解析 emberReactivity() 响应式绑定工厂

TanStack Table Ember 集成核心:深入解析 emberReactivity() 响应式绑定工厂 TanStack Table Ember 集成核心深入解析 emberReactivity() 响应式绑定工厂【免费下载链接】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导读emberReactivity()是 TanStack Table 为 Ember 框架提供的响应式适配层工厂函数它把 Ember 的tracked、cached、untrack等细粒度响应式原语翻译成 table-core 定义的TableReactivityBindings接口从而让 headless 表格核心在 Ember 组件内获得真正的框架原生响应式能力。读完本文你将掌握该函数的完整契约、每个绑定字段与 Ember 底层的对应关系、它在useTable中的装配位置以及外部 atoms 状态如何通过它接入 Ember 生命周期。函数签名与返回值emberReactivity()的官方类型定义位于关联文档其完整签名如下function emberReactivity(): TableReactivityBindings;入参无。返回值TableReactivityBindings——一个描述框架响应式能力集合的接口对象由 table-core 定义Ember、React、Solid、Vue、Svelte 等所有框架适配器都以“各自实现同一接口”的方式接入核心。定义位置packages/ember-table/declarations/reactivity.d.ts:2类型声明实际实现位于 packages/ember-table/src/reactivity.ts。从实现上看这是一个工厂函数每次调用都会返回一份全新的绑定对象并且内部会创建一个独立的订阅集合subscriptions保证每个表格实例拥有彼此隔离的响应式状态。emberReactivity同时也被导出为公共 API见 packages/ember-table/src/index.tsexport { emberReactivity } from ./reactivity.tsTableReactivityBindings框架适配层的统一契约要理解emberReactivity()返回了什么先要理解它实现的接口。TableReactivityBindings定义在 packages/table-core/src/core/reactivity/coreReactivityFeature.types.ts其设计意图在源码注释中写得很清楚Framework reactivity bindings used by table-core. Adapters (React, Solid, Vue, etc.) provide concrete implementations so core features can create derived/writable atoms and integrate with their scheduling primitives.也就是说table-core 不直接依赖任何框架它只依赖这组绑定方法来完成四类关键工作创建派生原子、创建可写原子、调度更新、解绑清理。接口包含以下字段字段类型作用createOptionsStoreboolean是否创建optionsStore下拉式选项存储供订阅者拉取最新 optionswrapExternalAtomsboolean是否包装外部传入的 atoms使其具备框架原生响应式读取能力createReadonlyAtom(fn, options?) ReadonlyAtomT由计算函数创建只读派生原子createWritableAtom(initialValue, options?) AtomT以初始值创建可写原子untrackT(fn: () T) T在不追踪响应式依赖的情况下求值batch(fn: () void) void批量合并响应式更新避免中间态重复计算schedule(fn: () void) void调度函数延迟执行通常在渲染阶段之后addSubscription(subscription) void登记订阅供卸载时统一清理unmount?() void表格销毁时的清理钩子commit?() void渲染阶段适配器提交受控状态后调用Ember 实现未使用可选作为对照同仓库的 packages/table-core/src/store-reactivity-bindings.ts 提供了基于tanstack/store的“无框架”实现batch/createAtom直接来自 store 库其addSubscription与unmount会直接抛错表示不支持。而 Ember 的适配则把这两项真正实现出来这正是 Ember 集成比纯 store 适配更完整的关键差异。Ember 原生原语如何映射到绑定字段packages/ember-table/src/reactivity.ts 的完整实现只有约 30 行但每一行都对应一个 Ember 响应式概念。逐字段对照如下export function emberReactivity(): TableReactivityBindings { const subscriptions new SetSubscription() return { createOptionsStore: true, wrapExternalAtoms: true, schedule: (fn) queueMicrotask(() fn()), batch: (fn) fn(), untrack, // cached createReadonlyAtom: T(fn: () T) { return computed(fn) }, // tracked createWritableAtom: T(value: T, options?: TableAtomOptionsT) { return signal(value, options) }, // Not for the ember integration, but for the tanstack inspector addSubscription: (subscription) { subscriptions.add(subscription) }, unmount: () { subscriptions.forEach((s) s.unsubscribe()) subscriptions.clear() }, } }createReadonlyAtom →computedcached派生值只读原子由computed(fn)创建。computed与ComputedSignal定义在 packages/ember-table/src/signal.tsexport class ComputedSignalT { cached get value() { return this.#compute() } }这里的cached来自glimmer/tracking它使派生值具备惰性求值 缓存语义只有被读取时才计算依赖变化后才重新计算。这正好满足ReadonlyAtom的“由 compute 函数派生”契约。subscribe subscribeNoEffect表示它不对外暴露订阅能力——因为 Ember 的读取依赖追踪tag tracking已经天然覆盖了派生值的更新传播。createWritableAtom →signaltracked可写原子可写原子由signal(value, options)创建返回SignalT类实例signal.ts。其核心是一个tracked _value字段export class SignalT { tracked _value // ... get value() { return this._value } set value(next: T) { const prev untrack(() this._value) const isEqual this.#options?.compare ? this.#options.compare(prev, next) : prev next if (isEqual) return this._value next for (const listener of this.#listeners) listener(next) } }值得注意的细节相等性短路赋值时会先通过compare选项或默认的比较新旧值相等则直接返回避免无意义的重渲染。双通道通知一方面tracked让模板/getter 中的读取自动建立 Glimmer tag 依赖另一方面内部维护#listeners集合供 table-core 通过addSubscription挂载的纯 JS 订阅者如constructTable中的外部 atom 同步逻辑、TanStack Inspector使用。源码注释明确指出这一点Ember 的读取是 tag 追踪的而这个 observer 列表是为了“core wires up”的普通 JS 订阅者准备的。set重载set同时接受普通值和更新函数(prev) next兼容tanstack/store的Atom契约。untrack →glimmer/validator的 untrack绑定直接把glimmer/validator的untrack原样暴露给 table-core。它用于在求值期间暂停依赖追踪。在 use-table.ts 中能看到它的典型用法// Untracked to prevent possible set on same computation as read errors in Ember. const initialOptions untrack(() userOptions.get())这里的注释解释了原因读取初始 options 时不建立依赖避免触发 Ember 的 “set on same computation as read” 运行时错误。schedule →queueMicrotaskbatch → 同步直通schedule: (fn) queueMicrotask(() fn()), batch: (fn) fn(),实现注释明确说明了调度策略的取舍timing is not important, but the main thing is that the work doesnothappen during the render phase.即不关心具体时机但绝不允许在渲染阶段执行调度的工作。因此 Ember 选择queueMicrotask把回调推迟到当前宏任务/渲染事务之后。而batch直接同步执行函数——因为tracked的更新本身会合并到 Ember 渲染队列中无需额外的批量层。在 useTable 中的装配coreReactivityFeatureemberReactivity()并不是被直接调用的顶层 API它由useTable在创建表格时自动注入。见 packages/ember-table/src/use-table.tsconst reactivity emberReactivity() const table constructTableTFeatures, TData({ ...initialOptions, features: { coreReactivityFeature: reactivity, ...initialOptions.features, }, // ... })coreReactivityFeature是TableFeatures中预留的专用插槽见 packages/table-core/src/core/coreFeatures.ts。把响应式绑定作为 feature 注入后table-core 内部的排序、分页、行模型等 feature 在创建自己的 atoms 时就会调用reactivity.createWritableAtom/reactivity.createReadonlyAtom从而产出基于tracked/cached的 Ember 原生原子。useTable本身是一个独立导出的函数详见函数参考与 use-table.ts它提供两个重载useTable(owner, getOptions)传入组件或任意 Ember destroyable作为第一个参数将外部 atom 的订阅绑定到该对象生命周期useTable(getOptions)无 owner 的独立表格用法。两种形式都会在内部调用emberReactivity()只是前者额外通过registerDestructor(owner, () reactivity.unmount?.())use-table.ts把unmount注册进 Ember 的销毁机制从而在组件销毁时统一退订所有外部订阅。wrapExternalAtoms外部 atoms 的 Ember 化包装emberReactivity()把wrapExternalAtoms设为true这标志着外部传入的 atoms 会被重新包装为 Ember 原生可读原子。包装逻辑同样位于 use-table.tsfor (const key of stateKeys) { const baseAtom table.baseAtoms[key]! atoms[key] reactivity.createReadonlyAtom(() { const externalAtom table.options.atoms?.[key] if (externalAtom) return externalAtom.get() const stateSlice table.options.state?.[key] if (stateSlice ! undefined) return stateSlice return baseAtom.get() }, { debugName: table/atoms/${key} }) }优先级为外部 atom options.state 中的受控状态切片 核心内部 baseAtom。经createReadonlyAtom即computed包装后cached派生值会追踪tracked依赖因此在模板或 getter 中读取table.store.state.sorting等切片时Ember 能精确地只重渲染发生变化的单元格。同时useTable还通过Object.defineProperty重写了table.options的 getter/setteruse-table.ts把选项访问也转换为惰性拉取pull-based并在liveOptions中保证 construct 期的规范化结果reactivity feature、包装后的 atoms优先于用户原始 options。实战在 Ember 组件中使用响应式原子官方示例 examples/ember/basic-external-atoms/app/templates/application.gts 完整演示了这套机制的实际用法。其核心骨架如下import { useTable, createAtom, ... } from tanstack/ember-table export default class BasicExternalAtomsTable extends Component { tracked data: ArrayPerson makeData(1_000) sortingAtom createAtomSortingState([]) paginationAtom createAtomPaginationState({ pageIndex: 0, pageSize: 10 }) table useTable(() ({ features, columns, data: this.data, atoms: { sorting: this.sortingAtom, pagination: this.paginationAtom, }, })) }这里的createAtomsignal.ts正是signal()的公开别名——它创建一个tracked备份的可写原子可直接传给options.atoms。由于emberReactivity()开启了wrapExternalAtomssortingAtom的读取在表格内部被computed包装于是组件中可以放心地这样读取get pagination() { return this.paginationAtom.get() }paginationAtom.get()在模板中建立 tag 依赖分页状态变化时模板自动更新反过来通过this.table.nextPage()等 API 修改表格内部状态时由于内部 atoms 同样是tracked的this.pagination也会同步刷新形成双向闭环的细粒度响应式。模板侧无需任何订阅代码直接消费派生数据即可{{#each this.rows as |row|}} tr {{#each (getAllCells row) as |cell|}} tdFlexRenderCell cell{{cell}} //td {{/each}} /tr {{/each}}与其他函数的关系与定位emberReactivity属于 Ember 包响应式基础设施的一部分与同目录下的其他导出函数协同工作computed创建cached派生值是createReadonlyAtom的底层signal与createAtom创建tracked可写原子是createWritableAtom的底层useTable实际调用emberReactivity()并注入coreReactivityFeature的装配入口。也就是说emberReactivity()是连接 table-core 通用算法与 Ember 专属响应式原语的那一层胶水table-core 只认TableReactivityBindingsEmber 只认tracked/cached/untrack而emberReactivity()用不到 40 行代码把二者无缝对接让 Ember 应用可以无侵入地使用 TanStack Table 的完整 headless 能力同时保持 Ember 惯用的模板响应式开发体验。【免费下载链接】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),仅供参考
返回列表