TanStack Alpine Table 排序(Sorting)完全指南:客户端排序、多列排序与服务端排序实战
TanStack Alpine Table 排序Sorting完全指南客户端排序、多列排序与服务端排序实战【免费下载链接】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 在 Alpine 框架下的适配包tanstack/alpine-table为对象系统讲解其排序Sorting特性的完整实现路径从启用rowSortingFeature与createSortedRowModel的初始化配置到排序状态state的三种管理方式、6 个内置排序函数的取舍、自定义排序函数的编写再到禁用排序、排序方向、多列排序multi-sorting、未定义值处理等全部可定制项。读完本篇你将能独立为 Alpine 应用接入可点击表头的客户端排序也能平滑切换到服务端手动排序manualSorting模式。快速上手Alpine 排序示例仓库中提供了一个完整可运行的排序示例含 1,000 行演示数据并内置 Stress Test (1M rows) 压力测试按钮可以直接对照阅读Alpine Sorting 示例入口为 index.html 与 src/main.ts示例还附带 Playwright 端到端冒烟测试 smoke.spec.ts验证了表格渲染、表头可见性以及点击 Regenerate Data 后首行数据确实变化等关键行为。一个重要的 Alpine 使用要点创建表格时请通过 getter 读取响应式输入例如用Alpine.reactive作为数据后备这样表格才能感知到数据更新。示例中正是这样实现的const local Alpine.reactive({ data: makeData(1_000) }) const table createTable({ features, columns, get data() { return local.data }, })排序功能启用Sorting Setup在 Alpine 中启用排序特性只需要在tableFeatures中注册特性与行模型import { createSortedRowModel, createTable, rowSortingFeature, sortFn_alphanumeric, sortFn_datetime, sortFn_text, tableFeatures, } from tanstack/alpine-table const features tableFeatures({ rowSortingFeature, sortedRowModel: createSortedRowModel(), // if using client-side sorting // manualSorting: true, // if using manual server-side sorting sortFns: { alphanumeric: sortFn_alphanumeric, datetime: sortFn_datetime, text: sortFn_text, }, }) const table createTable({ features, columns, get data() { return local.data }, })要点说明添加rowSortingFeature后排序相关的 API 与状态才会存在如果需要客户端排序还必须在其后配置sortedRowModel因为行模型插槽row model slots是类型检查的。在源码层面tanstack/alpine-table只是对tanstack/table-core的整体再导出见 packages/alpine-table/src/index.ts排序的核心实现位于 packages/table-core/src/features/row-sorting/其中 rowSortingFeature.ts 定义了状态与 APIcreateSortedRowModel.ts 实现了排序行模型。[!NOTE] 将整个内置注册表展开sortFns: { ...sortFns }仍然可用但会把每个内置排序函数都打进你的打包产物。推荐只注册你用到的函数或者直接把函数传给列的sortFn选项。默认的sortFn: auto会根据列的数据类型从注册表中解析为alphanumeric、text或datetime所以请注册你的列真正依赖的那些函数。源码中 sortFns.ts 也明确标注整体导出的sortFns注册表会破坏 tree-shaking被标记为deprecated。排序状态Sorting State排序状态被定义为对象数组结构如下type ColumnSort { id: string desc: boolean } type SortingState ColumnSort[]由于排序状态是数组因此可以同时对多列进行排序详见下文多列排序。读取排序状态表格的状态 atom 在 Alpine 中具有响应性。table.atoms.sorting.get()在 Alpine 绑定x-text、x-html、:value、x-if、x-for、x-effect或你Alpine.data对象上的 getter/方法内是一次响应式读取在事件处理器等未被跟踪的代码中同样的调用只是返回当前值。table.store.get()则返回一份当前完整状态的快照便于调试。table.atoms.sorting.get() // reactive read inside Alpine bindings, plain read elsewhere不过如果你需要在表格之外访问排序状态可以按下面介绍的方式“控制control”它。受控排序状态Controlled Sorting State如果你需要在应用的其他部分方便地访问排序状态可以自行拥有这段状态。v9 推荐的方式是通过atoms表格选项传入一个外部 atom。tanstack/store本来就是tanstack/alpine-table的依赖所以createAtom开箱即用。这个 atom 可以在别处被读取、写入或订阅比如用作服务端排序的 query key而无需让表格依赖组件局部状态。import { createAtom } from tanstack/store const sortingAtom createAtomSortingState([]) // can set initial sorting state here // subscribe to the atom wherever you need the value (e.g. for a query key) sortingAtom.subscribe(() { // react to sorting changes }) const table createTable({ features, columns, get data() { return local.data }, atoms: { sorting: sortingAtom, // table sorting APIs now update sortingAtom }, })此外v8 风格的state.sorting加onSortingChange模式仍然受支持适合简单集成或迁移 v8 代码。方式是在Alpine.reactive中持有状态切片const local Alpine.reactive({ sorting: [] as SortingState }) const table createTable({ features, columns, get data() { return local.data }, state: { get sorting() { return local.sorting // connect the reactive slice back down to the table }, }, onSortingChange: (updater) { local.sorting typeof updater function ? updater(local.sorting) : updater }, })两种受控方式的深入对比可参考 Table State 指南。初始排序状态Initial Sorting State如果你不需要在自己的状态管理或作用域内控制排序状态但仍想设置初始排序可以使用initialState表格选项而不是stateconst table createTable({ features, columns, get data() { return local.data }, initialState: { sorting: [ { id: name, desc: true, // sort by name in descending order by default }, ], }, })[!NOTE] 不要同时使用initialState.sorting和state.sorting因为受控的state.sorting值会覆盖initialState.sorting。客户端排序与服务端排序排序应与过滤、分页操作同一份数据集。如果服务端只返回一页或已过滤的子集客户端排序只能对已加载的这些行排序而不是完整数据集。完整的决策框架以及哪些场景下刻意混用客户端与服务端操作是合理的请参见 Client-Side vs Server-Side Guide。另外要注意客户端排序行模型在排序输入变化时会触发 page-index 自动重置钩子。页面索引是否重置取决于autoResetPageIndex、autoResetAll和manualPagination选项。如果排序是手动的且该行模型被省略或绕过排序状态变化不会触发该钩子——这时若需要重置服务端分页请在排序变化处理器中自行处理。手动服务端排序Manual Server-Side Sorting如果你计划在后端逻辑中自行完成服务端排序就不需要提供排序行模型。但如果你已经提供了排序行模型却想禁用它可以使用manualSorting表格选项import { createAtom } from tanstack/store const features tableFeatures({ rowSortingFeature }) // feature needed for sorting state/APIs const sortingAtom createAtomSortingState([]) const table createTable({ features, columns, get data() { return local.data }, manualSorting: true, // use pre-sorted row model instead of sorted row model atoms: { sorting: sortingAtom, }, })将排序状态提升到自己的作用域通过外部 atom 或state.sorting加onSortingChange模式的方法已在上文受控排序状态中介绍。此例中把外部 atom 订阅到 query key即可在服务端数据变更后自动重新请求。[!NOTE] 当manualSorting为true时表格会假设你提供的数据已经是排序好的不会再对其应用任何排序。客户端排序Client-Side Sorting实现客户端排序需要在 features 中添加rowSortingFeature和sortedRowModel工厂并从tanstack/alpine-table导入createSortedRowModel以及你要用的各个排序函数import { createSortedRowModel, createTable, rowSortingFeature, sortFn_alphanumeric, sortFn_datetime, sortFn_text, tableFeatures, } from tanstack/alpine-table const features tableFeatures({ rowSortingFeature, sortedRowModel: createSortedRowModel(), sortFns: { alphanumeric: sortFn_alphanumeric, datetime: sortFn_datetime, text: sortFn_text, }, }) const table createTable({ features, columns, get data() { return local.data }, })排序示例 src/main.ts 正是采用这套配置并为 10 列数据分别应用了字符串列默认升序、数字列默认降序、sortUndefined: last、invertSorting等典型列配置。排序行模型函数Sorting RowModelFns所有列的默认排序函数会根据列的数据类型自动推断。但为特定列精确指定排序函数通常很有用尤其是当数据可空或不属于标准数据类型时。可以用sortFn列选项为每一列指定自定义排序函数。默认情况下有 6 个内置排序函数可供选择alphanumeric— 混合字母数字值排序不区分大小写。较慢但如果字符串中包含需要自然排序的数字则更准确例如item2排在item10之前。alphanumericCaseSensitive— 混合字母数字值排序区分大小写。较慢但含数字字符串时更准确。text— 文本/字符串值排序不区分大小写。更快但如果字符串中包含数字则不太准确。textCaseSensitive— 文本/字符串值排序区分大小写。更快但不适合含数字的字符串。datetime— 按时间排序值类型为Date对象时使用。basic— 使用基本的a b ? 1 : a b ? -1 : 0比较。最快的排序函数但可能不够准确。从源码看这些函数都通过constructSortFn构建位于 packages/table-core/src/features/row-sorting/sortFns.ts例如sortFn_datetime在比较前用resolveDataValue把Date转为getTime()时间戳源码使用和而非因为 Date 对象即使时间相同也不相等sortFn_alphanumeric则先把值转为小写字符串再用分块算法逐块比较字符与数字见compareAlphanumeric这正是自然排序的实现来源。你也可以定义自己的自定义排序函数既可以内联作为sortFn列选项也可以按名称注册到你传给createSortedRowModel的排序函数注册表中。自定义排序函数Custom Sorting Functions无论是注册到createSortedRowModel的注册表还是直接作为sortFn列选项传递自定义排序函数都应具有以下签名// optionally use the SortFn to infer the parameter types const myCustomSortFn: SortFnTFeatures, TData ( rowA: RowTFeatures, TData, rowB: RowTFeatures, TData, columnId: string, ) { return // -1, 0, or 1 - access any row data using rowA.original and rowB.original }[!NOTE] 比较函数不需要考虑列是降序还是升序行模型会处理这部分逻辑。sortFn只需要提供一致的比较结果。每个排序函数接收两行和一个列 ID预期用列 ID 比较两行并返回-1、0或1升序语义。对照速查表返回值升序语义-1a b0a b1a b完整示例同时演示按名称引用内置函数、按名称引用注册的自定义函数、直接内联自定义函数三种方式const columns [ { header: () Name, accessorKey: name, sortFn: alphanumeric, // use built-in sorting function by name }, { header: () Age, accessorKey: age, sortFn: myCustomSortFn, // reference a custom sorting function registered with createSortedRowModel }, { header: () Birthday, accessorKey: birthday, sortFn: datetime, // recommended for date columns }, { header: () Profile, accessorKey: profile, // use custom sorting function directly sortFn: (rowA, rowB, columnId) { return rowA.original.someProperty - rowB.original.someProperty }, }, ] //... const features tableFeatures({ rowSortingFeature, sortedRowModel: createSortedRowModel(), sortFns: { alphanumeric: sortFn_alphanumeric, datetime: sortFn_datetime, myCustomSortFn: (rowA, rowB, columnId) rowA.original[columnId] rowB.original[columnId] ? 1 : rowA.original[columnId] rowB.original[columnId] ? -1 : 0, }, }) const table createTable({ features, columns, get data() { return local.data }, })TypeScript 提示要让sortFn: myCustomSortFn这样的字符串引用通过类型检查请把函数注册到tableFeatures的sortFns插槽上如上所示。该插槽就是注册表无需declare module增强。另一种做法是绕开注册表直接把函数传给sortFn列选项。示例仓库中的枚举列排序是一个很好的实战案例main.ts 用sortStatusFn把status列的枚举值single、complicated、relationship按自定义的statusOrder顺序排序而非字典序。自定义排序函数行为Customize Sorting Function Behavior排序函数支持一个可选的挂载hanging属性sortFn.resolveDataValue— 在比较两侧之前先对每行的值做归一化。所有用constructSortFn辅助函数构建的排序函数包括全部内置函数都会尊重它。constructSortFn用一个值级比较器sort加上可选解析器来构建排序函数。把比较逻辑留在sort、归一化留在resolveDataValue意味着某个现有排序函数的变体只需替换解析器。定义会挂载到返回的函数上所以你可以展开spread任何用constructSortFn构建的排序函数只覆盖不同的部分——这正是 sortFns.ts 的实现方式。例如忽略变音符号diacritics的alphanumeric变体让 Éric Bernard 排在 Eric Brandon 旁边而不是排在 Zak OSullivan 后面const stripDiacritics (value: string) value.normalize(NFD).replace(/\p{Diacritic}/gu, ) const alphanumericIgnoreDiacritics constructSortFn({ ...sortFn_alphanumeric, // reuse the comparator resolveDataValue: (value) stripDiacritics(sortFn_alphanumeric.resolveDataValue!(value)), }) const features tableFeatures({ rowSortingFeature, sortedRowModel: createSortedRowModel(), sortFns: { alphanumeric: sortFn_alphanumeric, alphanumericIgnoreDiacritics }, })同样的模式也适用于从零定义新的排序函数const byLastName constructSortFn({ sort: (dataValueA, dataValueB) dataValueA dataValueB ? 0 : dataValueA dataValueB ? 1 : -1, resolveDataValue: (value) String(value ?? ) .split( ) .at(-1) ?? , })自定义排序行为Customize Sorting表格和列有大量选项可以进一步定制排序的交互体验与行为。禁用排序Disable Sorting可以用enableSorting列选项或表格选项禁用某一列或整张表的排序const columns [ { header: () ID, accessorKey: id, enableSorting: false, // disable sorting for this column }, { header: () Name, accessorKey: name, }, //... ] //... const table createTable({ features, columns, get data() { return local.data }, enableSorting: false, // disable sorting for the entire table })排序方向Sorting Direction默认情况下使用toggleSortingAPI 循环列排序时字符串列第一次排序为升序数字列第一次排序为降序。可以用sortDescFirst列选项或表格选项改变这一行为const columns [ { header: () Name, accessorKey: name, sortDescFirst: true, // sort by name in descending order first (default is ascending for string columns) }, { header: () Age, accessorKey: age, sortDescFirst: false, // sort by age in ascending order first (default is descending for number columns) }, //... ] //... const table createTable({ features, columns, get data() { return local.data }, sortDescFirst: true, // sort by all columns in descending order first (default is ascending for string columns and descending for number columns) })[!NOTE] 建议在任何包含可空值的列上显式设置sortDescFirst列选项。如果列包含可空值表格可能无法正确判断该列是数字还是字符串。反转排序Invert Sorting反转排序不同于改变默认排序方向。如果某列的invertSorting列选项为truedesc/asc 排序状态仍会正常循环但行的实际排序会被反转。这对数值越小越好的倒置标度如排名 1st、2nd、3rd或高尔夫式计分非常有用const columns [ { header: () Rank, accessorKey: rank, invertSorting: true, // invert the sorting for this column. 1st - 2nd - 3rd - ... even if desc sorting is applied }, //... ]未定义值排序Sort Undefined Values任何 undefined 值都会根据sortUndefined列选项或表格选项被排到列表的开头或结尾。如果不指定sortUndefined的默认值是1undefined 值按较低优先级降序排序即升序时 undefined 出现在列表末尾。first— Undefined 值被推到列表开头last— Undefined 值被推到列表末尾false— Undefined 值像其他值一样传给排序函数不做特殊处理由排序函数自己负责-1— Undefined 值按较高优先级升序排序升序时 undefined 出现在列表开头1— Undefined 值按较低优先级降序排序升序时 undefined 出现在列表末尾[!NOTE]first和last选项在 v9 中可用。const columns [ { header: () Rank, accessorKey: rank, sortUndefined: -1, // first | last | 1 | -1 | false }, ]示例中 main.ts 对lastName与visits两列使用了sortUndefined: last确保有 null 值时这些列仍能稳定排序。移除排序Sorting Removal默认情况下在列上循环排序状态时可以移除排序。可以用enableSortingRemoval表格选项禁用此行为这在你想确保至少有一列始终处于排序状态时很有用。使用getToggleSortingHandler或toggleSortingAPI 时默认的循环行为如下第一个方向取决于列的数据类型与sortDescFirst选项见上文排序方向此处以字符串列为例none - asc - desc - none - asc - desc - ...如果禁用了排序移除none状态在第一次排序后就会被跳过none - asc - desc - asc - desc - ...一旦某列已排序且enableSortingRemoval为false在该列上切换排序永远不会移除排序。但如果用户排序了另一列且不是多排序事件排序会从上一列移除并只应用到新列。若想确保至少一列始终被排序请将enableSortingRemoval设为false。const table createTable({ features, columns, get data() { return local.data }, enableSortingRemoval: false, // disable the ability to remove sorting on columns (sorting can never return to none once applied) })多列排序Multi-Sorting如果使用column.getToggleSortingHandlerAPI多列排序默认是启用的。用户按住Shift键点击列表头时表格会在已排序的列基础上再对该列排序。如果使用column.toggleSortingAPI则必须手动传入是否使用多列排序column.toggleSorting(desc, multi)。禁用多列排序可以用enableMultiSort列选项或表格选项为特定列或整张表禁用多列排序。为特定列禁用多列排序时会用新列的排序替换所有现有排序const columns [ { header: () Created At, accessorKey: createdAt, enableMultiSort: false, // always sort by just this column if sorting by this column }, //... ] //... const table createTable({ features, columns, get data() { return local.data }, enableMultiSort: false, // disable multi-sorting for the entire table })自定义多列排序触发键默认使用Shift键触发多列排序。可以用isMultiSortEvent表格选项改变这一行为甚至可以指定所有排序事件都触发多列排序自定义函数返回trueconst table createTable({ features, columns, get data() { return local.data }, isMultiSortEvent: (e) true, // normal click triggers multi-sorting //or isMultiSortEvent: (e) e.ctrlKey || e.shiftKey, // also use the Ctrl key to trigger multi-sorting })多列排序上限默认情况下同时排序的列数没有限制。可以用maxMultiSortColCount表格选项设置上限const table createTable({ features, columns, get data() { return local.data }, maxMultiSortColCount: 3, // only allow 3 columns to be sorted at once })移除多列排序默认情况下移除多列排序是启用的。可以用enableMultiRemove表格选项禁用此行为const table createTable({ features, columns, get data() { return local.data }, enableMultiRemove: false, // disable the ability to remove multi-sorts })接入排序 UIWiring up the sort UI由于 Alpine 不会在通过x-html设置的内容里初始化指令表头内容要用x-htmlFlexRender({ header })渲染但点击处理器要挂在它外面的真实元素上并用事件调用getToggleSortingHandler返回的处理器th template x-if!header.isPlaceholder div :styleheader.column.getCanSort() ? cursor: pointer : clickheader.column.getToggleSortingHandler()?.($event) span x-htmlFlexRender({ header })/span span x-text({ asc: , desc: })[header.column.getIsSorted()] ?? /span /div /template /th真实示例 index.html 中的实现与之对应可排序列通过header.column.getCanSort()决定是否加sortable-header类与cursor: pointer样式点击事件调用getToggleSortingHandler()方向指示器则由sortIndicator(header.column.getIsSorted())返回的/文本渲染。数据变化时重置排序Reset Sorting When Data Changes默认情况下data选项变化时排序状态会被保留。设置autoResetSorting: true可以在处理新的数据引用时重置排序。重置会恢复initialState.sorting如果没有提供初始值则恢复为空排序状态。该选项只对数据变化做出响应。改变排序、过滤或分组不会触发它。全局的autoResetAll选项在被显式设置时会覆盖autoResetSorting。与手动/服务端排序组合使用时需小心服务端响应通常会替换data启用重置可能立刻清除请求该响应所用的排序状态。另外如果与服务端分页配合通常还应考虑autoResetPageIndex的取值排序变化时是否跳回第一页。排序 API 一览Sorting APIs排序相关的 API 非常丰富以下列出全部排序 API 及其典型用途table.setSorting— 直接设置排序状态。table.resetSorting— 将排序状态重置为初始状态或清空。column.getCanSort— 用于为列启用/禁用排序 UI。column.getIsSorted— 用于为列显示视觉排序指示器。column.getToggleSortingHandler— 用于为列接入排序 UI。可以挂到排序箭头图标按钮、菜单项或整个列表头单元格上。该处理器会用正确的参数调用column.toggleSorting。column.toggleSorting— 用于为列接入排序 UI。如果用它代替column.getToggleSortingHandler必须手动传入是否使用多列排序column.toggleSorting(desc, multi)。column.clearSorting— 用于为特定列提供清除排序按钮或菜单项。column.getNextSortingOrder— 用于显示列下一次将按哪个方向排序asc/desc/clear可放在 tooltip、菜单项或 aria-label 中。column.getFirstSortDir— 用于显示列第一次将按哪个方向排序asc/desc可放在 tooltip、菜单项或 aria-label 中。column.getAutoSortDir— 决定列第一次排序方向是升序还是降序。column.getAutoSortFn— 内部使用当列未指定排序函数时查找默认排序函数。column.getSortFn— 返回列当前实际使用的排序函数。column.getCanMultiSort— 用于启用/禁用列的多列排序 UI。column.getSortIndex— 用于在多列排序场景中显示列的排序序号第一、第二、第三……个被排序的列例如徽标或指示器。小结TanStack Alpine Table 的排序能力覆盖了从开箱即用的客户端排序到完全自定义的服务端手动排序的完整光谱通过rowSortingFeature加createSortedRowModel一行即可启用SortingState的数组结构天然支持多列排序6 个内置sortFn_*函数配合sortFn列选项与constructSortFn辅助函数既能按列精确选型也能以极小成本扩展出忽略变音符号按姓氏排序等自定义变体。在 Alpine 场景下务必记住两点数据通过 getter 响应式读取表头点击 UI 用真实元素包裹x-html渲染的内容。结合 Alpine Sorting 示例 与源码 row-sorting 模块你可以快速在 Alpine 应用中落地一整套专业、可扩展的表格排序体验。【免费下载链接】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),仅供参考