
Refine useTable 过滤能力详解从 filters 状态到 setFilters 的 merge/replace 行为【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine本篇聚焦 Refine 核心 HookuseTable的 Filtering过滤能力如何基于filters状态和setFilters函数用不受控的输入框、下拉框等原生 UI 实时驱动服务端过滤请求并掌握merge/replace两种过滤合并行为、initialFilter/permanentFilter配置以及过滤状态清空的正确姿势。读完后你可以按文中完整示例搭出一个可复制运行的实时过滤列表页并能对照仓库源码理解每次setFilters调用背后的合并与去重逻辑。useTable 过滤能力在整体设计中的位置useTable是 Refine 提供的 headless无界面表格 Hook底层通过useList发起数据请求按照排序、过滤与分页状态拉取数据UI 完全由使用者自行处理。过滤只是它三类状态sorter / filter / pagination之一官方 API 参考中 Filtering 一节的说明是useTablehas a filter feature. The filter is done by using thefiltersstate. Thefiltersstate is aCrudFilterstype that contains the field, the operator, and the value of the filter. You can change the filter state by using thesetFiltersfunction. Every change will trigger a new fetch.即filters是一个CrudFilters数组每一项包含field、operator、value三要素任何一次setFilters都会触发新的getList请求。启用syncWithLocation后过滤状态还会自动同步进 URL 查询参数。完整属性说明initialFilter、permanentFilter、defaultSetFilterBehavior等见 useTable API 文档。完整实战示例三个实时过滤控件驱动服务端请求过滤实时预览文档 给出的示例挂载在/posts路由对http://localhost:3000/posts这个 REST 端点发起请求用原生input和select实现了三个过滤条件按title模糊匹配contains、按id精确匹配eq、按status精确匹配eq。核心代码如下import React, { useMemo } from react; import { IResourceComponentsProps, useMany, useTable, HttpError, } from pankod/refine-core; interface IPost { id: number; title: string; content: string; status: published | draft | rejected; createdAt: string; } const PostList: React.FCIResourceComponentsProps () { const { tableQueryResult, filters, setFilters } useTable IPost, HttpError (); // Fetches the posts for the current page const posts tableQueryResult?.data?.data ?? []; // Gets the current filter values for the fields const currentFilterValues useMemo(() { // Filters can be a LogicalFilter or a ConditionalFilter. ConditionalFilter not have field property. So we need to filter them. // We use flatMap for better type support. const logicalFilters filters.flatMap((item) field in item ? item : [], ); return { title: logicalFilters.find((item) item.field title)?.value || , id: logicalFilters.find((item) item.field id)?.value || , status: logicalFilters.find((item) item.field status)?.value || , }; }, [filters]); return ( div div style{{ display: flex, gap: 1rem, alignItems: center, marginBottom: 4px, }} input placeholderSearch by title value{currentFilterValues.title} onChange{(e) { setFilters([ { field: title, operator: contains, value: !!e.currentTarget.value ? e.currentTarget.value : undefined, }, ]); }} / input placeholderSearch by id value{currentFilterValues.id} onChange{(e) { setFilters([ { field: id, operator: eq, value: !!e.currentTarget.value ? e.currentTarget.value : undefined, }, ]); }} / select value{currentFilterValues.status} onChange{(e) setFilters( [ { field: status, operator: eq, value: !!e.currentTarget.value ? e.currentTarget.value : undefined, }, ], replace, ) } option valueAll/option option valuepublishedPublished/option option valuedraftDraft/option option valuerejectedRejected/option /select /div h1Posts/h1 table thead tr thID/th thTitle/th thStatus/th thCreated At/th /tr /thead tbody {tableQueryResult.data?.data.map((post) ( tr key{post.id} td{post.id}/td td{post.title}/td td{post.status}/td td{new Date(post.createdAt).toDateString()}/td /tr ))} /tbody /table /div ); };组件通过setRefineProps({ resources: [{ name: posts, list: PostList }] })注册为posts资源的列表页。逐段拆解这个示例1. 状态来源useTable 返回的 filters / setFiltersconst { tableQueryResult, filters, setFilters } useTableIPost, HttpError()中filters是当前过滤状态数组CrudFilterstableQueryResult是底层useList的查询结果data.data为当前页记录。注意示例中useTable没有传入任何属性因此resource默认从路由读取setFilters默认采用merge行为。2. 反向回填 UI用 useMemo 从 filters 提取当前值三个控件都不是各自维护useState而是受控于filters状态本身。currentFilterValues用useMemo从filters数组中按field取值这里有一个容易踩坑的类型细节filters数组中的元素既可能是LogicalFilter形如{ field, operator, value }也可能是ConditionalFilter形如{ operator: or/and, value: [...], key? }。后者没有field属性直接find((item) item.field ...)会报类型错误示例用filters.flatMap((item) (field in item ? item : []))把ConditionalFilter从联合类型中剔除得到纯LogicalFilter[]。注释中也说明用flatMap是为了更好的类型支持利用 TypeScript 的 narrowing 将元素收窄为LogicalFilter取值时用|| 兜底保证输入框在没有对应过滤条件时回显为空字符串。3. 触发请求onChange 里调用 setFilters空值传 undefined每次输入变化都会调用setFilters([{ field, operator, value }])useTable随即以新的过滤条件重新请求列表。两个关键设计清除过滤靠undefined当输入框被清空时value传undefined。官方文档明确提示——在merge行为下若要移除某个过滤条件应把该条件的value设为undefined或null若是or类过滤则应设为空数组[]id与title用默认mergestatus下拉框显式传replacesetFilters的第二个参数可以指定本次调用的合并行为。下拉框的 All 选项把status过滤值置为undefined并用replace整体替换避免残留旧条件详见下一节行为解析。源码解析setFilters 的三种执行路径与 unionFilters 合并算法结合当前仓库 useTable 源码 可以看到setFilters并非单一函数而是根据参数分派到三条路径源码中的SetFilterBehavior定义为merge | replace// packages/core/src/hooks/useTable/index.ts节选 const setFiltersAsMerge useCallback( (newFilters: CrudFilter[]) { setFilters((prevFilters) unionFilters(preferredPermanentFilters, newFilters, prevFilters), ); }, [preferredPermanentFilters], ); const setFiltersAsReplace useCallback( (newFilters: CrudFilter[]) { setFilters(unionFilters(preferredPermanentFilters, newFilters)); }, [preferredPermanentFilters], ); const setFiltersWithSetter useCallback( (setter: (prevFilters: CrudFilter[]) CrudFilter[]) { setFilters((prev) unionFilters(preferredPermanentFilters, setter(prev)), ); }, [preferredPermanentFilters], ); const setFiltersFn useCallback( ( setterOrFilters, behavior: SetFilterBehavior prefferedFilterBehavior, ) { if (typeof setterOrFilters function) { setFiltersWithSetter(setterOrFilters); } else { if (behavior replace) { setFiltersAsReplace(setterOrFilters); } else { setFiltersAsMerge(setterOrFilters); } } }, [setFiltersWithSetter, setFiltersAsReplace, setFiltersAsMerge], );函数式 setter 形式优先如果第一个参数是函数则按setter(prevFilters) CrudFilters处理等价于 React 的setState函数式更新适用于基于上一帧状态做局部修改的场景merge 路径默认调用unionFilters(permanentFilters, newFilters, prevFilters)把新旧过滤条件按同字段同操作符的语义合并replace 路径只传unionFilters(permanentFilters, newFilters)不带prevFilters即整体替换但 permanent 过滤仍然保留默认行为可配置behavior的默认值来自配置项新版源码中为filtersFromProp?.defaultBehavior ?? mergev3 文档中对应defaultSetFilterBehavior属性setFilters的第二参数可逐次覆盖它。merge 行为的具体算法unionFilters真正的合并逻辑在 definitions/table/index.ts 的unionFilters中// packages/core/src/definitions/table/index.ts节选 export const unionFilters ( permanentFilter: CrudFilter[], newFilters: CrudFilter[], prevFilters: CrudFilter[] [], ): CrudFilter[] { // ...对顶层多个无 key 的 or/and 条件过滤发出 warnOnce 警告... return unionWith( permanentFilter, newFilters, prevFilters, compareFilters, ).filter( (crudFilter) crudFilter.value ! undefined crudFilter.value ! null (crudFilter.operator ! or || (crudFilter.operator or crudFilter.value.length ! 0)) (crudFilter.operator ! and || (crudFilter.operator and crudFilter.value.length ! 0)), ); };从源码结构看它基于 lodash 的unionWith按compareFilters比较fieldoperator条件过滤还会比较key去重合并优先级顺序为permanent 过滤 新过滤 旧过滤。这精确对应了文档对merge的语义描述——新过滤条件与已有过滤条件同字段时覆盖该字段的旧条件不同字段时则追加。更重要的是末尾的.filter(...)合并结果会剔除value为undefined/null的条目以及value为空数组的or/and条件——这就是把value设为undefined即可在 merge 行为下移除该过滤条件这一技巧的底层机制setFilters时先保留占位合并阶段再统一清掉无效条目。initialFilter / permanentFilter初始与永久过滤API 文档还定义了两类静态过滤类型为CrudFilter[]useTable({ initialFilter: [ { field: title, operator: contains, value: Foo }, ], });initialFilter只作用于初始状态用户变更过滤后会被清掉permanentFilter永久生效、不可被 UI 清除即使用户改变了过滤状态也会一直参与请求。源码印证了二者如何生效useTable初始化状态时用setInitialFilters(permanent, initial)合并且无论走 merge 还是 replace 路径permanentFilter都被作为unionFilters的第一个高优先级参数传入——所以 permanent 条件永远存活且不会被replace行为清掉。对应的单元测试见 useTable 测试文件其中覆盖了defaultBehavior/replace相关的断言。syncWithLocation过滤状态编码进 URL若启用syncWithLocationuseTable会把过滤状态自动编码到 URL 查询参数URL 变化时状态也随之更新便于分享/收藏特定过滤视图。从 源码的同步副作用 可以看到写入 URL 前会用differenceWith(filters, preferredPermanentFilters, isEqual)先剔除 permanent 过滤再序列化避免永久条件被冗余地写进链接。版本适用性说明需要说明适用前提本文示例与属性名基于仓库中version-3.xx.xx版本的 API 文档使用的是pankod/refine-core包、tableQueryResult/filters等 v3 返回字段以及顶层initialFilter/permanentFilter/defaultSetFilterBehavior属性。而当前仓库packages/core主干源码已演进为更新的主版本 API返回字段改为tableQuery/currentPage/result过滤配置收纳进filters对象initial/permanent/defaultBehavior/mode。两条主线上merge/replace的双行为语义与unionFilters合并算法是一致的但如果你在当前主干版本上运行示例属性与返回值的命名需按 主干 useTable 类型定义 调整。可进一步查阅的仓库文件过滤实时预览片段本文示例来源useTable 完整 API 参考属性与返回值表useTable Hook 实现unionFilters / setInitialFilters 过滤合并工具useTable 单元测试【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考