首页 / TanStack 生态入门教程 / 排序与过滤

TanStack 生态入门教程

排序与过滤

本教程共 38 篇 · 第 28 篇 · 更新于 2026-07-27 · 约 11 分钟阅读

TanStackTanStack 生态入门教程TanStack Table排序过滤SortingFilteringfuzzy-filtering

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 加点击事件
  // ...
}

三个关键配置:

  1. getSortedRowModel: getSortedRowModel() — 引入排序行模型。
  2. state.sorting — 把排序状态传给表格。
  3. 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 常见坑

坑一:排序和过滤状态没有受控。 不传 stateonXxxChange,表格内部能管状态,但你在别处读不到当前排序/过滤状态。需要和外部交互(如同步到 URL)时必须受控。

坑二:排序方向只有升序降序,没有「取消」。 默认点击循环是 升序 → 降序 → 升序。想要 升序 → 降序 → 取消,设 enableSortingRemoval: true(某些版本默认就是 true,检查一下)。

坑三:全局过滤不生效。 检查三个:有没有传 getFilteredRowModel、有没有设 state.globalFilteronGlobalFilterChange、列定义有没有设 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-utilsrankItem,自定义 fuzzyFilter 过滤函数。
  • 手动模式:服务端排序过滤用 manualSorting / manualFiltering,表格只管状态不管逻辑。

下一章讲分页和分组,让你的表格能处理大量数据和做数据聚合。