排序与过滤
本教程共 38 篇 · 第 28 篇 · 更新于 2026-07-27 · 约 11 分钟阅读
28. 排序与过滤
本节目标:给表格加上排序和过滤功能。学会用
getSortedRowModel管理排序状态、用getFilteredRowModel做列过滤和全局过滤,还能用模糊匹配(fuzzy filtering)实现搜索框。学完你的表格能点表头排序、能输关键词过滤数据。
28.1 排序和过滤的底层逻辑
上一章讲了行模型是流水线,排序和过滤就是流水线上的两道工序:
核心行模型(所有行)
↓ getSortedRowModel 按排序列排序
↓ getFilteredRowModel 按过滤条件筛选
→ 渲染
排序改变行的顺序,过滤减少行的数量。两者都是在上一步结果基础上加工,互不干扰。
28.2 排序:getSortedRowModel
28.2.1 开启排序
要排序,得做三件事:传入排序行模型、管理排序状态、让表头能点。
import { useReactTable, getCoreRowModel, getSortedRowModel, flexRender } from '@tanstack/react-table'
function SortableTable() {
// sorting 状态的管理下面讲,先看整体结构
// const [sorting, setSorting] = useState<SortingState>([])
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), // 排序行模型
// state: { sorting },
// onSortingChange: setSorting,
})
// 渲染表头时,给 th 加点击事件
// ...
}
三个关键配置:
getSortedRowModel: getSortedRowModel()— 引入排序行模型。state.sorting— 把排序状态传给表格。onSortingChange— 排序变化时的回调,更新状态。
28.2.2 排序状态管理
排序状态是个数组,每项描述一列的排序方向:
// sorting 的结构
[
{ id: 'age', desc: false }, // age 列升序
{ id: 'name', desc: true }, // name 列降序
]
数组意味着支持多列排序(先按第一项排,相同再按第二项排)。默认只单列排序,要开启多列排序得设 enableMultiSort: true。
用 React 的 useState 管理排序状态:
import { useState } from 'react'
import type { SortingState } from '@tanstack/react-table'
function SortableTable() {
const [sorting, setSorting] = useState<SortingState>([
{ id: 'age', desc: false }, // 默认按 age 升序
])
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
state: { sorting },
onSortingChange: setSorting,
})
// ...
}
Note这种「状态在外部管理、传给表格」的模式叫受控状态(Controlled State)。TanStack Table 的所有状态都可以受控,也可以让表格自己管(非受控)。第 31 章会详细讲状态管理。
28.2.3 让表头可点击排序
表头要能点击触发排序,用 header.column.getToggleSortingHandler():
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id} onClick={header.column.getToggleSortingHandler()}>
{flexRender(header.column.columnDef.header, header.getContext())}
{/* 显示排序方向箭头 */}
{{ asc: ' ↑', desc: ' ↓' }[header.column.getIsSorted() as string] ?? ''}
</th>
))}
</tr>
))}
</thead>
getToggleSortingHandler() 返回一个点击处理器,点一下在「升序 → 降序 → 取消」之间循环。getIsSorted() 返回当前排序状态:false(未排序)、'asc'(升序)、'desc'(降序)。
Tip某些列不想让用户排序,在列定义里设
enableSorting: false。点了也不会触发排序。
28.2.4 自定义排序函数
默认排序按字符串或数字比较。需要特殊排序逻辑时,在列定义里指定 sortingFn:
const columns = [
columnHelper.accessor('age', {
header: '年龄',
sortingFn: 'alphanumeric', // 内置排序函数
}),
columnHelper.accessor('status', {
header: '状态',
// 自定义排序函数:active 排前面
sortingFn: (rowA, rowB) => {
const order = { active: 0, inactive: 1 }
return order[rowA.original.status] - order[rowB.original.status]
},
}),
]
内置排序函数有这些:
'auto'(默认):自动选合适的'alphanumeric':字母数字混合排序'alphanumericCaseSensitive':区分大小写'text':文本排序'datetime':日期时间排序'basic':基本数值比较
28.3 过滤:getFilteredRowModel
28.3.1 开启过滤
过滤和排序类似,也要引入行模型、管理状态:
import { getFilteredRowModel } from '@tanstack/react-table'
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(), // 过滤行模型
state: { columnFilters },
onColumnFiltersChange: setColumnFilters,
})
28.3.2 列过滤(Column Filtering)
列过滤是针对单列的过滤。每列可以有自己的过滤条件。比如「姓名列包含某个字」:
const columns = [
columnHelper.accessor('name', {
header: '姓名',
// 定义这列怎么过滤:包含传入的值
filterFn: 'includesString',
}),
columnHelper.accessor('age', {
header: '年龄',
filterFn: 'inNumberRange', // 数值范围过滤
}),
]
在 UI 上设置列过滤值:
// 给姓名列设过滤值
table.getColumn('name')?.setFilterValue('张')
// 清除某列过滤
table.getColumn('name')?.setFilterValue(undefined)
columnFilters 状态的结构:
[
{ id: 'name', value: '张' }, // name 列过滤值为 "张"
{ id: 'age', value: [20, 30] }, // age 列过滤值 20-30
]
28.3.3 内置过滤函数
TanStack Table 提供了不少内置过滤函数:
| 过滤函数 | 用途 | 示例 |
|---|---|---|
includesString | 包含字符串(不区分大小写) | 搜”张”匹配”张三” |
includesStringSensitive | 包含字符串(区分大小写) | 搜”Z”不匹配”z” |
equalsString | 完全等于 | 搜”active”只匹配”active” |
arrIncludes | 数组包含某元素 | 标签数组包含”js” |
arrIncludesAll | 数组包含所有元素 | 标签同时含”js”和”ts” |
inNumberRange | 数值在范围内 | 年龄 20-30 |
equals | 严格相等 | 状态等于”active” |
weakEquals | 宽松相等 | == 比较 |
Note
filterFn也可以写成自定义函数:filterFn: (row, columnId, filterValue) => { ... return true/false }。返回true表示这行通过过滤,false表示被过滤掉。
28.3.4 全局过滤(Global Filtering)
列过滤是每列单独过滤,全局过滤是「一个搜索框过滤所有列」。比如用户在搜索框输入”张”,姓名列、地址列、备注列都搜一遍。
const [globalFilter, setGlobalFilter] = useState('')
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
state: { globalFilter },
onGlobalFilterChange: setGlobalFilter,
// 全局过滤函数
globalFilterFn: 'includesString',
})
UI 上加个搜索框:
<input
value={globalFilter ?? ''}
onChange={(e) => setGlobalFilter(e.target.value)}
placeholder="搜索所有列..."
/>
全局过滤的工作原理:对每一行,遍历所有可过滤的列,只要有任意一列匹配,这行就保留。默认用 auto 过滤函数,会根据值的类型自动选。
Tip全局过滤和列过滤可以同时用,结果是两者的交集—既要满足列过滤条件,又要满足全局搜索条件。
28.4 模糊匹配(Fuzzy Filtering)
默认的 includesString 是精确子串匹配:搜”张三”只能匹配”张三”,搜”三张”匹配不到。如果想要「输”三张”也能搜到”张三”」的效果,就要用模糊匹配(Fuzzy Filtering)。
28.4.1 装模糊匹配库
TanStack Table 没有内置模糊匹配,推荐用 @tanstack/match-sorter-utils:
npm install @tanstack/match-sorter-utils
28.4.2 自定义模糊过滤函数
写一个 fuzzy 过滤函数,注册到表格里:
import { rankItem } from '@tanstack/match-sorter-utils'
import type { FilterFn } from '@tanstack/react-table'
// 定义 fuzzy 过滤函数
const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => {
const itemRank = rankItem(row.getValue(columnId), value)
addMeta({ itemRank }) // 存排序信息,用于按匹配度排序
return itemRank.passed // 返回是否匹配
}
28.4.3 在表格中使用
把 fuzzy 过滤函数注册到表格,然后用在全局过滤上:
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
filterFns: { fuzzy: fuzzyFilter }, // 注册自定义过滤函数
globalFilterFn: 'fuzzy', // 全局过滤用 fuzzy
state: { globalFilter },
onGlobalFilterChange: setGlobalFilter,
})
filterFns 注册自定义过滤函数,globalFilterFn 指定全局过滤用哪个。也可以在列定义里单独指定某列用 fuzzy:filterFn: 'fuzzy'。
Warning
rankItem默认处理字符串。如果列值是数字或对象,可能需要先转成字符串再匹配,否则结果不符合预期。
28.4.4 按匹配度排序
模糊匹配的 rankItem 返回匹配等级(rankingInfo),可以用来给结果按匹配度排序,匹配度高的排前面:
import { compareItems, rankItem } from '@tanstack/match-sorter-utils'
import { sortingFns, type SortingFn } from '@tanstack/react-table'
// 排序函数:按模糊匹配度排
const fuzzySort: SortingFn = (rowA, rowB, columnId) => {
let dir = 0
if (rowA.columnFiltersMeta[columnId]) {
dir = compareItems(
rowA.columnFiltersMeta[columnId].itemRank,
rowB.columnFiltersMeta[columnId].itemRank,
)
}
return dir === 0 ? sortingFns.alphanumeric(rowA, rowB, columnId) : dir
}
const columns = [
columnHelper.accessor('name', {
header: '姓名',
filterFn: 'fuzzy',
sortingFn: fuzzySort, // 用模糊匹配排序
}),
]
这样用户搜”张”,“张三”(精确匹配)排前面,“小张”(部分匹配)排后面,体验更好。
28.5 完整的排序过滤示例
把排序、列过滤、全局过滤组合起来:
import { useState } from 'react'
import {
useReactTable,
getCoreRowModel,
getSortedRowModel,
getFilteredRowModel,
flexRender,
} from '@tanstack/react-table'
import type { SortingState, ColumnFiltersState } from '@tanstack/react-table'
const data = [
{ id: 1, name: '张三', age: 28, city: '北京' },
{ id: 2, name: '李四', age: 34, city: '上海' },
{ id: 3, name: '王五', age: 22, city: '广州' },
{ id: 4, name: '赵六', age: 30, city: '深圳' },
]
const columns = [
{ header: '姓名', accessorKey: 'name' },
{ header: '年龄', accessorKey: 'age' },
{ header: '城市', accessorKey: 'city' },
]
function FullTable() {
const [sorting, setSorting] = useState<SortingState>([])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const [globalFilter, setGlobalFilter] = useState('')
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
state: { sorting, columnFilters, globalFilter },
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onGlobalFilterChange: setGlobalFilter,
})
return (
<div>
{/* 全局搜索 */}
<input
value={globalFilter}
onChange={(e) => setGlobalFilter(e.target.value)}
placeholder="全局搜索..."
className="border px-2 py-1 mb-2"
/>
<table className="border-collapse">
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th
key={header.id}
onClick={header.column.getToggleSortingHandler()}
className="border border-gray-300 px-4 py-2 bg-gray-50 cursor-pointer"
>
{flexRender(header.column.columnDef.header, header.getContext())}
{{ asc: ' ↑', desc: ' ↓' }[header.column.getIsSorted() as string] ?? ''}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="border border-gray-300 px-4 py-2">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}
28.6 手动排序和过滤(服务端模式)
数据量大时,排序和过滤放在服务端做更合适。TanStack Table 支持手动模式(Manual):表格不管排序过滤逻辑,只负责管理状态,你拿状态去请求接口。
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
// 手动模式:告诉表格排序和过滤在服务端做
manualSorting: true,
manualFiltering: true,
state: { sorting, columnFilters },
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
})
// 排序或过滤状态变化时,重新请求服务端
useEffect(() => {
fetchData({ sorting, columnFilters })
}, [sorting, columnFilters])
设了 manualSorting: true 后,getSortedRowModel 就不需要了(甚至可以不传),因为表格不在前端排序,而是等你从服务端拿到排好序的数据。
Warning手动模式下,排序/过滤状态的变化不会自动触发请求。你得自己监听状态变化,在
useEffect里发请求。这是和前端模式最大的区别。
28.7 常见坑
坑一:排序和过滤状态没有受控。 不传 state 和 onXxxChange,表格内部能管状态,但你在别处读不到当前排序/过滤状态。需要和外部交互(如同步到 URL)时必须受控。
坑二:排序方向只有升序降序,没有「取消」。 默认点击循环是 升序 → 降序 → 升序。想要 升序 → 降序 → 取消,设 enableSortingRemoval: true(某些版本默认就是 true,检查一下)。
坑三:全局过滤不生效。 检查三个:有没有传 getFilteredRowModel、有没有设 state.globalFilter 和 onGlobalFilterChange、列定义有没有设 enableColumnFilter: false(全局过滤会跳过禁用过滤的列)。
坑四:accessorFn 列过滤值对不上。 用 accessorFn 的列,getValue() 返回的是函数计算结果。过滤时如果函数返回的不是字符串,includesString 可能不匹配。需要自定义 filterFn 处理。
坑五:模糊匹配对中文效果不理想。 @tanstack/match-sorter-utils 的 fuzzy 匹配主要针对英文优化,中文场景下拼音、分词匹配需要额外处理。简单的中文场景用 includesString 往往够用。
28.8 小结
这一章给表格加了排序和过滤:
- 排序:传
getSortedRowModel,管理sorting状态,表头用getToggleSortingHandler()触发排序。 - 列过滤:传
getFilteredRowModel,管理columnFilters状态,每列用filterFn定义过滤逻辑。 - 全局过滤:用
globalFilter状态,一个搜索框过滤所有列。 - 模糊匹配:用
@tanstack/match-sorter-utils的rankItem,自定义fuzzyFilter过滤函数。 - 手动模式:服务端排序过滤用
manualSorting/manualFiltering,表格只管状态不管逻辑。
下一章讲分页和分组,让你的表格能处理大量数据和做数据聚合。