如何在TanStack Table(V8)中实现带多操作符的服务端列过滤
解决方案:TanStack Table V8 对接 nestjs-paginate 服务端过滤
核心思路:自定义列元数据(ColumnMeta)
TanStack Table 没有内置对接特定后端过滤格式的方案,最灵活的方式是给列配置自定义元数据,用来指定该列的默认过滤操作符、值解析规则,再通过转换函数把 ColumnFiltersState 转成后端兼容格式。
1. 定义自定义列元数据类型
先扩展 TanStack Table 的 ColumnMeta,加入过滤相关配置:
import { ColumnMeta } from '@tanstack/react-table'; import { FilterOperator } from './path-to-nestjs-paginate-types'; // 自定义列过滤元数据 export interface FilterColumnMeta extends ColumnMeta { // 该列默认使用的过滤操作符 defaultFilterOperator?: FilterOperator; // 可选:解析过滤组件返回值的函数(比如把日期范围转成$btw需要的格式) valueParser?: (value: unknown) => string | string[]; }
2. 给列配置过滤元数据
在列定义时,针对不同类型的列(文本、日期范围、多选下拉等)配置对应的元数据:
const columns = [ { accessorKey: 'username', header: '用户名', meta: { defaultFilterOperator: FilterOperator.ILIKE, // 模糊匹配 } as FilterColumnMeta, }, { accessorKey: 'age', header: '年龄', meta: { defaultFilterOperator: FilterOperator.EQ, // 默认精确匹配 } as FilterColumnMeta, }, { accessorKey: 'createTime', header: '创建时间', meta: { defaultFilterOperator: FilterOperator.BTW, // 时间范围查询 // 把日期组件返回的[start, end]数组转成后端需要的逗号分隔格式 valueParser: (value: [string, string]) => `${value[0]},${value[1]}`, } as FilterColumnMeta, }, { accessorKey: 'role', header: '角色', meta: { defaultFilterOperator: FilterOperator.IN, // 多选IN查询 // 把多选下拉返回的数组转成逗号分隔字符串 valueParser: (value: string[]) => value.join(','), } as FilterColumnMeta, }, ];
3. 编写过滤条件转换函数
把 ColumnFiltersState 转换成后端需要的格式,同时处理空值、特殊操作符:
import { ColumnFiltersState } from '@tanstack/react-table'; import { FilterOperator } from './path-to-nestjs-paginate-types'; import { FilterColumnMeta } from './path-to-your-meta-type'; export const transformColumnFilters = ( columnFilters: ColumnFiltersState, columns: typeof columns ) => { return columnFilters.map(filter => { const column = columns.find(col => col.accessorKey === filter.id); if (!column) return null; const meta = column.meta as FilterColumnMeta; const operator = meta?.defaultFilterOperator || FilterOperator.EQ; let value = filter.value; // 用自定义解析器处理组件返回值 if (meta?.valueParser && value != null) { value = meta.valueParser(value); } // 处理"为空"过滤场景 if (value === null) { return { column: filter.id, filter: { operator: FilterOperator.NULL, value: '' }, }; } // 忽略空字符串过滤条件 if (typeof value === 'string' && value.trim() === '') { return null; } return { column: filter.id, filter: { operator, value }, }; }).filter(Boolean); // 过滤无效转换结果 };
4. 在组件中使用转换函数
在 UsersList 组件里调用转换函数,把过滤条件传给后端请求:
... const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]); // 转换过滤条件 const transformedFilters = transformColumnFilters(columnFilters, columns); const { data, isFetching, isLoading } = useGetUsersQuery({ search: globalFilter, page: pageIndex + 1, limit: pageSize, sortBy: sorting.map((s) => `${s.id}:${s.desc ? 'DESC' : 'ASC'}`).join(','), columnFilters: transformedFilters, }); ...
同时支持客户端/服务端过滤
通过 manualFiltering 属性切换模式,核心是:
- 服务端模式:用
manualFiltering: true,过滤变化时触发后端请求 - 客户端模式:请求全量数据,用 TanStack Table 内置的过滤函数处理
1. 自定义表格组件处理双模式
import { useReactTable } from '@tanstack/react-table'; const CustomTable = ({ columns, data, manualFiltering, onFilterChange }) => { const table = useReactTable({ columns, data, manualFiltering, // 开启手动过滤模式(服务端用) onColumnFiltersChange: onFilterChange, // 客户端过滤时,配置对应后端操作符的过滤函数 filterFns: { ilike: (rowValue, filterValue) => { if (!filterValue) return true; return String(rowValue).toLowerCase().includes(String(filterValue).toLowerCase()); }, btw: (rowValue, filterValue) => { if (!filterValue) return true; const [start, end] = filterValue.split(','); return new Date(rowValue) >= new Date(start) && new Date(rowValue) <= new Date(end); }, // 其他操作符对应的客户端过滤函数... }, }); return ( // 表格渲染逻辑(略) ); };
2. 父组件切换逻辑
const UsersList = ({ manualFiltering = false }) => { const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]); const [localData, setLocalData] = React.useState([]); // 客户端模式下请求全量数据,服务端模式下带过滤参数请求 const { data: serverData } = useGetUsersQuery({ ...(manualFiltering ? { page: 1, limit: 9999 } // 客户端过滤时拉全量数据 : { search: globalFilter, page: pageIndex + 1, limit: pageSize, sortBy: sorting.map((s) => `${s.id}:${s.desc ? 'DESC' : 'ASC'}`).join(','), columnFilters: transformColumnFilters(columnFilters, columns), }), }); // 客户端模式下把全量数据存本地 React.useEffect(() => { if (serverData && manualFiltering) { setLocalData(serverData.data); } }, [serverData, manualFiltering]); return ( <CustomTable columns={columns} data={manualFiltering ? localData : (data?.data || [])} manualFiltering={manualFiltering} onFilterChange={setColumnFilters} // 分页、排序等其他属性(略) /> ); };
注意事项
- 空值处理:清空过滤框时要忽略该条件,避免后端收到无效参数
- 操作符一致性:客户端过滤函数的逻辑要和后端操作符完全匹配(比如
ILIKE对应不区分大小写的模糊匹配) - 类型安全:用 TypeScript 约束所有类型,避免
unknown类型的value导致报错
内容的提问来源于stack exchange,提问作者monotype
相关产品推荐
相关产品推荐

