Refine 中使用 useSelect Hook 驱动 Mantine Select 下拉选择器:完整实战指南
2026/9/13 15:26:53 网站建设 项目流程

Refine 中使用 useSelect Hook 驱动 Mantine Select 下拉选择器:完整实战指南

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

导读

useSelect是 Refine 为 Mantine UI 集成提供的核心数据 Hook,它把数据提供器(dataProvider)中的资源记录自动转换为 Mantine<Select>组件的选项(options),是表单页、筛选栏中最常用的"外键下拉选择"方案。读完本文,你将掌握useSelect的基础接入方式、全部配置属性(排序、过滤、分页、搜索、默认值、实时更新等)、与useForm及 CRUD 组件的集成模式,并能结合源码理解其底层数据获取机制(useList+useMany)。

一、useSelect 的核心机制:从资源到 Select 选项

useSelectHook 用于在需要把某条资源(resource)的记录作为下拉选项时,管理 Mantine 的<Select>组件。

其工作机制非常简单:

  • 数据获取依赖useListHook:useSelect底层通过useList调用 dataProvider 的getList方法获取记录列表,因此useList支持的所有数据属性(paginationsortersfiltersmeta等)在useSelect中同样可用。关于useList的完整说明见 useList Hook 文档。
  • 返回一个selectProps对象,可直接展开(spread)到 Mantine<Select>组件上,实现零胶水代码接入。

在 packages/mantine/src/hooks/useSelect/index.ts 中可以看到,Mantine 版的useSelect是 Refine 核心useSelect的薄封装:

export const useSelect = <...>(props: UseSelectProps<...>): UseSelectReturnType<...> => { const { query, defaultValueQuery, onSearch, options } = useSelectCore<...>(props); return { selectProps: { data: options, onSearchChange: onSearch, searchable: true, filterDataOnExactSearchMatch: true, clearable: true, }, query, defaultValueQuery: defaultValueQuery.query, }; };

从源码可以看出,Mantine 集成层会自动为selectProps注入几个开箱即用的行为:

属性作用
dataoptions由数据记录转换而来的选项数组
searchabletrue默认开启搜索输入框
onSearchChangeonSearch搜索值变化时触发服务端搜索
filterDataOnExactSearchMatchtrue当搜索值精确匹配选中项时保留该数据
clearabletrue允许一键清空当前选择

因此你只需要一行<Select {...selectProps} />,即可获得一个可搜索、可清空、数据自动加载的下拉选择器。

二、基础用法:最小可运行示例

这是useSelect最基础的使用方式,也是 文档 live preview 示例 的核心代码(已去掉文档站点的 live preview 包装,可直接放入你的页面组件):

import { useSelect } from "@refinedev/mantine"; import { Select } from "@mantine/core"; interface ICategory { id: number; title: string; } const ProductCreate: React.FC = () => { const { selectProps } = useSelect<ICategory>({ resource: "categories", }); return ( <Select label="Category" placeholder="Select a category" withinPortal {...selectProps} /> ); };

关键点说明:

  • resource: "categories"指定要拉取哪个资源的记录作为选项,它最终会作为getList方法的参数(通常对应 API 路径)传入,经由useList执行查询。
  • 泛型ICategory用于约束记录类型,便于 TypeScript 推导选项数据结构。
  • withinPortal是 Mantine Select 自身的属性,让下拉浮层渲染在 portal 中,避免被父容器裁剪,在弹窗/抽屉表单中尤其重要。
  • labelplaceholder等均为 Mantine Select 原生 props,useSelect只负责提供数据相关的selectProps,二者互不干扰。

三、属性详解:把数据查询与展示完全掌控

1. resource 与 identifier

resource是必填属性,会作为参数传给getList。注意它通常被用作 API 端点路径,具体语义取决于 dataProvider 中getList的实现方式,可参考 创建 data provider 了解 resource 如何被处理:

useSelect({ resource: "categories", });

如果你有多个同名的资源,可以传入identifier替代nameidentifier只作为资源的主匹配键,dataProvider 方法仍会使用<Refine>组件中定义的资源name。详见 Refine 组件文档中的 identifier 说明。

2. optionLabel 与 optionValue:自定义选项的 label 和 value

用于改变选项的valuelabel字段,默认值分别为optionLabel = "title"optionValue = "id"

useSelect<ICategory>({ resource: "products", optionLabel: "name", optionValue: "productId", });

这两个属性都支持三种写法:

  • 字符串字段名optionLabel: "name"
  • 嵌套路径:支持 Object path 中,字符串形式的optionLabel/optionValue是通过 lodash 的get(item, path)取值的,这正是嵌套路径得以生效的原因。
  • 函数:函数会接收item参数,返回最终 label/value:
const { options } = useSelect({ optionLabel: (item) => `${item.firstName} ${item.lastName}`, optionValue: (item) => item.id, });

3. searchField:指定搜索字段

指定onSearch搜索时匹配哪个字段。默认取optionLabel的值(当optionLabel是字符串时),否则使用title字段。这一默认逻辑同样体现在核心源码的默认参数中:searchField = typeof optionLabel === "string" ? optionLabel : "title"(见 packages/core/src/hooks/useSelect/index.ts)。

const { onSearch } = useSelect({ searchField: "name" }); onSearch("John"); // 按 name 字段搜索值为 John 的记录

optionLabel为函数时,searchField回退为title

const { onSearch } = useSelect({ optionLabel: (item) => `${item.id} - ${item.name}`, }); onSearch("John"); // 按 title 字段搜索

4. sorters:控制选项顺序

sorters会通过useList作为参数传给getList,用于向 API 发送排序查询参数:

useSelect({ sorters: [ { field: "title", order: "asc", }, ], });

CrudSorting的完整类型定义见 Interface References 文档:每个 sort 项由fieldorder"asc""desc")组成。你也可以像 排序 live preview 示例 那样,把排序状态交给 React state 动态切换:

const [order, setOrder] = React.useState<"asc" | "desc">("asc"); const { selectProps } = useSelect<ICategory>({ resource: "categories", sorters: [ { field: "title", order, }, ], }); <Button onClick={() => setOrder(order === "asc" ? "desc" : "asc")}>Toggle Order</Button> <Select {...selectProps} />;

5. filters:按条件筛选选项

filters同样通过useList传给getList,向 API 发送过滤查询参数:

useSelect({ filters: [ { field: "isActive", operator: "eq", value: true, }, ], });

CrudFilters/CrudFilter的类型定义同样见 Interface References 文档,其中operator支持eqneltgtcontainsinbetweennull等一整套CrudOperators

6. defaultValue 与 selectedOptionsOrder:默认选中值

defaultValue允许你让某些选项默认被选中,并向<Select>追加额外选项。一个典型场景是:当数据量很大需要分页时,默认值可能不在当前可见选项中,这会导致<Select>显示异常。为避免这种情况,useSelect会额外发起一次useMany查询,用defaultValue去后端拉取对应记录并追加到选项数组中,从而保证默认值始终存在于选项列表。

useSelect({ defaultValue: 1, // 或 [1, 2],支持单个值或数组 });

关于useMany的详细说明见 useMany Hook 文档。

selectedOptionsOrder用于控制这些默认选中选项的排序位置:

  • "in-place"(默认):选中的选项排在底部;
  • "selected-first":选中的选项排在顶部。
useSelect({ defaultValue: 1, // 或 [1, 2] selectedOptionsOrder: "selected-first", // in-place | selected-first });

7. debounce:防抖搜索

onSearch函数进行防抖,单位为毫秒(核心源码默认值为300):

useSelect({ debounce: 500, });

8. queryOptions 与 defaultValueQueryOptions:查询行为定制

queryOptions用于向底层useQuery传递额外选项,例如重试次数:

useSelect({ queryOptions: { retry: 3, }, });

当指定了defaultValue时,useSelect内部会用useMany查询选中的记录。defaultValueQueryOptions可单独定制这次查询的选项;若不传,则沿用queryOptions中的值(见 packages/core/src/hooks/useSelect/index.ts):

const { options } = useSelect({ defaultValueQueryOptions: { onSuccess: (data) => { console.log("triggers when on query return on success"); }, }, });

9. pagination:分页控制

pagination通过useList传给getList,向 API 发送分页查询参数:

useSelect({ pagination: { currentPage: 2, // 当前页 }, }); useSelect({ pagination: { pageSize: 20, // 每页条数 }, }); useSelect({ pagination: { mode: "off", // "off" | "client" | "server" }, });

mode决定使用哪种分页策略:"server"表示服务端分页(配合defaultValue+useMany兜底最常用),"client"表示客户端分页,"off"表示不分页、一次拉取全部数据。在真实示例 examples/base-mantine/src/pages/posts/create.tsx 中,分类与标签两个下拉都使用了pagination: { mode: "server" }

10. onSearch:服务端自动补全(Autocomplete)

onSearch让选项支持"自动补全"式搜索。传入的函数接收搜索值,返回一组CrudFilter,覆盖原有的filters进行服务端查询:

const { selectProps } = useSelect<ICategory>({ resource: "categories", onSearch: (value) => [ { field: "title", operator: "contains", value, }, ], }); <Select label="Category" placeholder="Select a category" withinPortal {...selectProps} />;

完整可运行示例见 onSearch live preview。核心机制是:selectProps.onSearchChange被绑定到内部onSearch,输入变化即触发携带过滤条件的服务端查询。

客户端过滤:如果你希望完全在客户端过滤选项,可以将onSearch传为undefined来禁用服务端过滤,并自行接管搜索状态:

const { selectProps } = useSelect({ resource: "categories", }); const [searchValue, onSearchChange] = useState(""); <Select {...selectProps} onSearch={undefined} onSearchChange={onSearchChange} searchValue={searchValue} />;

11. meta:向 dataProvider 传递附加信息

meta用于向 dataProvider 方法传递附加信息,典型用途有两个:

  • 针对特定用例定制 dataProvider 方法行为;
  • 用普通 JavaScript 对象(JSON)生成 GraphQL 查询。

例如向getList传递自定义请求头:

useSelect({ meta: { headers: { "x-meta-data": "true" }, }, });

对应的 dataProvider 实现中即可解构出meta并加以利用:

const myDataProvider = { //... getList: async ({ resource, pagination, sorters, filters, meta }) => { const headers = meta?.headers ?? {}; const url = `${apiUrl}/${resource}`; const { data } = await httpClient.get(`${url}`, { headers }); return { data }; }, //... };

12. dataProviderName:多数据提供器选择

当应用中配置了多个 dataProvider 时,用dataProviderName指定本次查询使用哪一个:

useSelect({ dataProviderName: "second-data-provider", });

13. 通知定制:successNotification 与 errorNotification

这两个属性依赖NotificationProvider才能生效。

数据获取成功后,useSelect可调用NotificationProvideropen方法展示成功通知,可用successNotification定制内容:

useSelect({ successNotification: (data, values, resource) => { return { message: `${data.title} Successfully fetched.`, description: "Success with no errors", type: "success", }; }, });

获取失败时同理,用errorNotification定制错误通知:

useSelect({ errorNotification: (data, values, resource) => { return { message: `Something went wrong when getting ${data.id}`, description: "Error", type: "error", }; }, });

14. 实时更新:liveMode、onLiveEvent 与 liveParams

以下属性均要求配置了LiveProvider才能生效。

useSelect挂载时,它会向liveProvidersubscribe方法传递channelresource等参数,用于订阅实时更新。

  • liveMode:决定收到相关 live 事件后是否自动更新数据,"auto"自动更新,"manual"手动处理:
useSelect({ liveMode: "auto", });
  • onLiveEvent:订阅到新事件时的回调函数:
useSelect({ onLiveEvent: (event) => { console.log(event); }, });
  • liveParams:传递给liveProvidersubscribe方法的附加参数。

15. overtimeOptions:请求超时提示

当请求耗时过长时,可以通过overtimeOptions展示加载提示。interval是检查间隔(毫秒),onInterval是每个间隔触发的回调。返回的overtime.elapsedTime表示已耗时(毫秒),请求完成时变为undefined

const { overtime } = useSelect({ //... overtimeOptions: { interval: 1000, onInterval(elapsedInterval) { console.log(elapsedInterval); }, }, }); console.log(overtime.elapsedTime); // undefined, 1000, 2000, 3000 4000, ... // 可以这样使用: { elapsedTime >= 4000 && <div>this takes a bit longer than expected</div>; }

四、与 useForm 及 CRUD 组件集成

useSelect最常见的实战场景是配合useFormCreate/Edit组件构建表单。文档的 CRUD live preview 示例 展示了完整写法:

import { Create, useForm, useSelect } from "@refinedev/mantine"; import { Select } from "@mantine/core"; interface ICategory { id: number; title: string; } const ProductCreate: React.FC = () => { const { saveButtonProps, getInputProps, errors } = useForm({ initialValues: { category: { id: "", }, }, }); const { selectProps } = useSelect<ICategory>({ resource: "categories", }); return ( <Create saveButtonProps={saveButtonProps}> <form> <Select mt={8} label="Category" placeholder="Select a category" {...getInputProps("category.id")} {...selectProps} /> </form> </Create> ); };

要点:

  • useFormgetInputProps("category.id")负责表单值的双向绑定(选中后写入category.id),selectProps负责选项数据的加载与渲染,两者通过展开操作合并到同一个<Select>上,互不冲突。
  • 在真实项目 examples/base-mantine/src/pages/posts/create.tsx 中,还演示了更复杂的组合:status使用静态data数组、category使用useSelect加载分类、tags使用另一个useSelect实例配合 Mantine<MultiSelect>实现多选,并在getInputProps("tags")之上叠加tagSelectProps,同时自定义了filter函数做客户端过滤。

五、FAQ 实战问答

1. 如何给选项添加搜索(Autocomplete)?

使用onSearch函数即可,它是设置搜索值的函数,会触发表单/选项的重新查询。简单示例如上文 onSearch 章节 所示。

2. 如何确保 defaultValue 一定出现在选项中?

当手头只有记录的id时,可以把它作为defaultValue传入。Hook 会通过useMany发起请求获取对应数据,并将其标记为已选中,从而保证它一定显示在下拉框中。示例见 defaultValue live preview。

3. 如何修改选项的 label 和 value?

使用optionLabeloptionValue,默认值分别是titleid。例如改为namecategoryId

useSelect({ optionLabel: "name", optionValue: "categoryId", });

4. 可以手动创建选项吗?

可以。当仅靠optionLabel/optionValue不够用时,可以直接从query返回值手动构造选项数组:

const { query } = useSelect(); const options = query.data?.data.map((item) => ({ label: item.title, value: item.id, })); return <Select options={options} />;

5. 如何与 CRUD 组件、useForm 一起使用?

直接参考 第四节 的完整示例:将getInputProps("category.id")selectProps同时展开到<Select>上即可。

六、返回值与类型参数速查

返回值

属性说明类型
selectPropsMantine Select 的 propsSelectPropsType
query列表查询结果QueryObserverResult<{ data: TData }>
defaultValueQuery默认值记录的查询结果QueryObserverResult<{ data: TData }>
defaultValueQueryOnSuccess默认值查询成功回调() => void
overtime超时加载状态{ elapsedTime?: number }

其中selectProps中的核心字段为:

属性说明类型
data用于渲染下拉项的数据(string \| SelectItem)[]
searchable是否开启搜索boolean
onSearchChange搜索值变化时触发(query: string) => void
filterDataOnExactSearchMatch搜索值精确匹配选中项时是否过滤数据boolean

类型参数

参数说明默认值
TQueryFnData查询函数返回的数据类型,继承BaseRecordBaseRecord
TError自定义错误类型,继承HttpErrorHttpError
TDataselect函数返回的数据类型;不指定时默认取TQueryFnDataTQueryFnData

七、更进一步

  • 想亲手运行useSelect的完整示例,可查看仓库中的 base-mantine 示例项目,其中包含配套的createeditlistshow页面。
  • 深入理解底层数据获取:阅读 useList Hook 文档 与 useMany Hook 文档。
  • 查看 Mantine 集成层源码 packages/mantine/src/hooks/useSelect/index.ts 与核心实现 packages/core/src/hooks/useSelect/index.ts,了解optionLabel/optionValue的 lodash 路径取值、debounce默认值 300ms、searchField回退逻辑等实现细节。
  • 若需完整掌握 Mantine 生态下的其他 Hook,可继续浏览 ui-integrations/mantine/hooks 目录 下的useFormuseDrawerForm等文档。

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询