refine 数据层过滤机制深度解析:CrudFilters 类型体系与 or/and 过滤逻辑的实现

发布时间:2026/9/13 21:09:57
refine 数据层过滤机制深度解析:CrudFilters 类型体系与 or/and 过滤逻辑的实现
refine 数据层过滤机制深度解析CrudFilters 类型体系与 or/and 过滤逻辑的实现【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine本文基于 refine 官方文档《Handling Filters》展开系统讲解 refine 数据层Data Provider的过滤模型CrudFilters类型体系、LogicalFilter的 AND 语义、ConditionalFilter的 or/and 组合语义以及如何在自定义 Data Provider 中解析并落地这些过滤条件。读完后你将能够把前端收集的筛选参数翻译成后端可执行的查询语句并理解 refine 官方各数据提供方Strapi、Supabase、Hasura、NestJS CRUD 等内部对过滤条件的具体处理代码。1. 概述refine 对过滤条件的统一约定refine 的核心约束之一是所有 Data Provider 的getList等方法统一接收一个CrudFilters类型的数组来按字段值过滤结果。这意味着你可以使用任意多个过滤条件数组里放多少条就有多少条可以使用or运算符把多个条件组合成或逻辑进一步嵌套出复杂查询UI 层如useTable、TableFilter或列表页筛选表单收集到的条件经过 Router 序列化后最终原样传入 Data Provider由 Data Provider 自行决定如何映射到后端 API。这种前端描述逻辑、Data Provider 负责翻译的设计正是 refine 能够适配 REST、GraphQL、NoSQL 等多种后端的根本原因——过滤条件本身是后端无关backend-agnostic的中间表示。在 refine 的 monorepo 中这套类型体系的权威定义位于 core 包packages/core/src/contexts/data/types.ts。2.CrudFilters类型体系与运算符文档给出的基础类型定义如下v3 文档版本// Supported operators: type CrudOperators | eq | ne | lt | gt | lte | gte | in | nin | contains | ncontains | containss | ncontainss | between | nbetween | null | nnull | or | startswith | nstartswith | startswiths | nstartswiths | endswith | nendswith | endswiths | nendswiths; // Supported filter types: type LogicalFilter { field: string; operator: ExcludeCrudOperators, or; value: any; }; type ConditionalFilter { operator: or; value: LogicalFilter[]; }; type CrudFilter LogicalFilter | ConditionalFilter; //highlight-next-line type CrudFilters CrudFilter[];各运算符的语义可从当前仓库 core 包中紧邻类型定义的注释表packages/core/src/contexts/data/types.ts得到确认运算符含义eq/ne等于 / 不等于不区分大小写lt/gt/lte/gte小于 / 大于 / 小于等于 / 大于等于in/nin值包含于数组 / 不包含于数组contains/ncontains包含 / 不包含子串containss/ncontainss包含 / 不包含区分大小写s sensitivebetween/nbetween介于区间内 / 不在区间内null/nnull为空 / 不为空startswith/nstartswith以…开头 / 不以…开头startswiths/nstartswiths以…开头区分大小写/ 否endswith/nendswith以…结尾 / 不以…结尾endswiths/nendswiths以…结尾区分大小写/ 否or条件组合运算符仅用于ConditionalFilter版本演进说明上表是 v3 文档中的运算符集合。当前仓库 core 包的源码在此基础上做了扩充packages/core/src/contexts/data/types.ts新增eqs/nes大小写敏感的等于/不等于新增ina/nina数组字段的包含/不包含判断新增and逻辑运算符ConditionalFilter的operator从固定的or变为ExtractCrudOperators, or | and且value允许嵌套ConditionalFilter即支持任意深度的 or/and 混合嵌套ConditionalFilter增加了可选的key字段用途见第 5 节。export type LogicalFilter { field: string; operator: ExcludeCrudOperators, or | and; value: any; }; export type ConditionalFilter { key?: string; operator: ExtractCrudOperators, or | and; value: (LogicalFilter | ConditionalFilter)[]; }; export type CrudFilter LogicalFilter | ConditionalFilter; export type CrudFilters CrudFilter[];这一演进意味着文档中v3 时代只能 or 组合的写法在新版本里可以直接升级为 or/and 任意嵌套类型层面即可保证组合结构合法。3.LogicalFilter天然的 AND 语义顶层CrudFilters数组中的每一条LogicalFilter之间默认是AND关系。例如按name和age两个字段过滤const filter [ { field: name, operator: eq, value: John, }, { field: age, operator: lt, value: 30, }, ];等价查询name John AND age 30。这里的关键理解是AND 并不是显式写出来的运算符而是数组顶层这一结构本身隐含的语义。官方 Data Provider 在实现时都遵循这一约定——例如 Strapi 提供方的过滤生成逻辑会把顶层多条条件直接以拼接进请求 URLpackages/strapi/src/dataProvider.ts多条条件天然是交集关系。4.ConditionalFilter显式的 or/and 组合当需要表达或逻辑时使用ConditionalFilter它的operator为or或新版本的andvalue是LogicalFilter或更深嵌套的条件数组。文档示例——满足 (nameJohn Doe 且 age30) 或 (nameJR.Doe 且 age1)const filter [ { operator: or, value: [ { operator: and, value: [ { field: name, operator: eq, value: John Doe, }, { field: age, operator: eq, value: 30, }, ], }, { operator: and, value: [ { field: name, operator: eq, value: JR.Doe, }, { field: age, operator: eq, value: 1, }, ], }, ], }, ];等价查询(name John Doe AND age 30) OR (name JR.Doe AND age 1)。顶层多个 ConditionalFilter 必须加key如果在一个顶层数组里放多个ConditionalFilter必须为每一条添加key否则控制台会报警告且多个条件可能无法被正确组合。文档示例const filter [ { key: parent, operator: or, value: [ { operator: and, value: [ { field: name, operator: eq, value: John Doe }, { field: age, operator: eq, value: 30 }, ], }, { operator: and, value: [ { field: name, operator: eq, value: Jane Doe }, { field: age, operator: eq, value: 28 }, ], }, ], }, { key: children, operator: or, value: [ { operator: and, value: [ { field: name, operator: eq, value: JR John }, { field: age, operator: eq, value: 1 }, ], }, { operator: and, value: [ { field: name, operator: eq, value: JR Jane }, { field: age, operator: eq, value: 2 }, ], }, ], }, ];key的实际作用体现在序列化链路中过滤条件经常要经过 URL query string 的序列化/反序列化例如useTable把筛选状态写进路由多个同构的ConditionalFilter若无key区分Router 层就无法可靠地还原哪条是哪个组合逻辑就会丢失。类型层面这一点也已落实——当前 core 源码中ConditionalFilter带有可选的key?: stringpackages/core/src/contexts/data/types.ts。5. 组合实战多条件混合查询实际业务中常见同一查询中混合逻辑条件与条件组。文档给出的例子是找出 createdAt 为两个可能日期之一、且状态为 published 的文章filter [ { operator: or, value: [ { field: createdAt, operator: eq, value: 2022-01-01, }, { field: createdAt, operator: eq, value: 2022-01-02, }, ], }, { operator: eq, field: status, value: published, }, ];等价查询status published AND (createdAt 2022-01-01 OR createdAt 2022-01-02)。注意这个例子里第二个元素的写法operator: eq直接放在顶层对象上它是被容忍的简写——判断是否为逻辑过滤的依据是operator ! or operator ! and field in filter而不是对象字段顺序。这也解释了第 6 节中 Data Provider 的判别代码为什么长这样。6. 在 Data Provider 中处理过滤条件文档给出的处理骨架是遍历filters按是否条件组分流处理import { DataProvider } from pankod/refine-core; const dataProvider (): DataProvider ({ getList: async ({ resource, pagination, filters, sort }) { if (filters) { filters.map((filter) { if (filter.operator ! or filter.operator ! and field in filter) { // Handle your logical filters here // console.log(typeof filter); // LogicalFilter } else { // Handle your conditional filters here // console.log(typeof filter); // ConditionalFilter } }); } }, });注这是 v3 文档示例包名为pankod/refine-core当前 monorepo 中对应包已更名为refinedev/core判别逻辑保持一致。下面用仓库中两个官方实现来印证这套分流模式如何真正落地。6.1 Strapi 提供方拼接查询字符串老版 Strapi 提供方的 generateFilter 完整实现了文档骨架中的两条分支const generateFilter (filters?: CrudFilters) { let rawQuery ; if (filters) { filters.map((filter) { if ( filter.operator ! or filter.operator ! and field in filter ) { const { field, operator, value } filter; if (operator eq) { rawQuery ${field}${value}; } else { if (Array.isArray(value)) { value.map((val) { rawQuery [${field}_${operator}]${val}; }); } else { rawQuery [${field}_${operator}]${value}; } } } else { const value filter.value as LogicalFilter[]; value.map((item, index) { const { field, operator, value } item; rawQuery _where[_or][${index}][${field}_${operator}]${value}; }); } }); } return rawQuery; };可以逐条读出它对后端 API 的假设逻辑过滤eq特殊处理为顶层参数fieldvalueStrapi 对简单等于条件的约定其余运算符拼成 Strapi 的动态参数格式[field_operator]value例如[age_lt]30数组值会展开为多个同名参数对应in之类运算符。条件过滤固定翻译为 Strapi 的where查询_where[_or][i][field_op]value用下标i区分或分支。生成的查询串在 getList 中被拼到getList与count两个请求的 URL 上保证分页 total 也受同样的过滤条件约束——这是一个容易遗漏但很实用的细节。6.2 REST 包中的 Strapi v4 提供方生成嵌套 JSON 过滤对象新版 Strapiv4改用 JSON 过滤体monorepo 中 packages/rest/src/data-providers/strapi-v4/utils/generateFilter.ts 展示了更完整的递归翻译方式const generateLogicalFilter (filter: LogicalFilter): any { const { field, operator, value } filter; const mappedOperator mapOperator(operator); const fieldPath generateNestedFilterField(field); // ... 逐层构造嵌套对象末端为 { [$${mappedOperator}]: value } }; const generateConditionalFilter (filter: ConditionalFilter): any { const result: any {}; const operatorKey $${filter.operator}; // $or 或 $and const subFilters filter.value.map((item) { if (item.operator ! or item.operator ! and field in item) { return generateLogicalFilter(item); } return generateConditionalFilter(item); // 递归支持任意深度嵌套 }); result[operatorKey] subFilters; return result; }; export const generateFilter (filters?: CrudFilters): any { if (!filters || filters.length 0) { return {}; } let result: any {}; filters.forEach((filter) { let filterObj: any; if ( filter.operator ! or filter.operator ! and field in filter ) { filterObj generateLogicalFilter(filter); } else { filterObj generateConditionalFilter(filter); } result mergeFilters(result, filterObj); // 顶层多条条件合并为 AND }); return result; };几个值得注意的实现细节运算符映射refine 的eq/ne/contains等运算符先经过 mapOperator 转成目标后端的$eq/$ne等键名这是后端无关中间表示 → 具体后端语法的标准做法嵌套字段路径generateNestedFilterField支持address.city点号路径以及[field]括号写法将其拆成多层嵌套对象从而能过滤关联字段顶层合并即 AND多条顶层过滤对象通过mergeFilters深度合并——同一个字段路径下多条条件会合并到同一层对象中这与顶层数组 AND的语义完全一致条件组即$or/$andgenerateConditionalFilter以$or/$and为键包装子条件数组且对子项递归因此第 4 节的 or 里套 and、and 里再套 or 的结构都能被完整翻译。该工具函数配有单元测试 index.spec.tsNestJS CRUD 提供方的等价实现 handleFilter.ts 及其测试 handleFilter.spec.ts 也验证了同样的分流与组合逻辑。6.3 官方支持 or/and 逻辑的 Data Provider文档明确列出了支持or/and过滤逻辑的官方 Data Provider在当前仓库中对应的源码位置为NestJS CRUDpackages/nestjsx-crudStrapipackages/strapiStrapi v4packages/strapi-v4Supabasepackages/supabaseHasurapackages/hasura如果你使用官方未覆盖的后端例如自研 REST API就需要按照第 6 节的分流骨架自行实现or/and的翻译——从源码结构看判断依据始终是三元表达式filter.operator ! or filter.operator ! and field in filter这是各官方实现共享的最小判别式。7. 小结refine 用CrudFiltersLogicalFilter与ConditionalFilter的数组作为跨后端的统一过滤描述顶层数组隐含 ANDor/and条件组显式表达其他逻辑关系运算符集合以 core 包 types.ts 为准当前版本相比 v3 文档新增了eqs/nes/ina/nina以及and组合运算符且条件组支持任意深度嵌套顶层出现多个ConditionalFilter时必须提供key否则序列化还原与组合逻辑可能失效Data Provider 侧的处理范式固定为分流 → 翻译Strapi 翻译成 URL 动态参数或where查询Strapi v4 / NestJS CRUD 翻译成$or/$and嵌套过滤体官方实现可分别参考 packages/strapi/src/dataProvider.ts、packages/rest/src/data-providers/strapi-v4/utils/generateFilter.ts 与 packages/rest/src/data-providers/nestjsx-crud/utils/handleFilter.ts 作为自定义实现的蓝本。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考