You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 23:35:18