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支持的所有数据属性(pagination、sorters、filters、meta等)在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注入几个开箱即用的行为:
| 属性 | 值 | 作用 |
|---|---|---|
data | options | 由数据记录转换而来的选项数组 |
searchable | true | 默认开启搜索输入框 |
onSearchChange | onSearch | 搜索值变化时触发服务端搜索 |
filterDataOnExactSearchMatch | true | 当搜索值精确匹配选中项时保留该数据 |
clearable | true | 允许一键清空当前选择 |
因此你只需要一行<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 中,避免被父容器裁剪,在弹窗/抽屉表单中尤其重要。label、placeholder等均为 Mantine Select 原生 props,useSelect只负责提供数据相关的selectProps,二者互不干扰。
三、属性详解:把数据查询与展示完全掌控
1. resource 与 identifier
resource是必填属性,会作为参数传给getList。注意它通常被用作 API 端点路径,具体语义取决于 dataProvider 中getList的实现方式,可参考 创建 data provider 了解 resource 如何被处理:
useSelect({ resource: "categories", });如果你有多个同名的资源,可以传入identifier替代name。identifier只作为资源的主匹配键,dataProvider 方法仍会使用<Refine>组件中定义的资源name。详见 Refine 组件文档中的 identifier 说明。
2. optionLabel 与 optionValue:自定义选项的 label 和 value
用于改变选项的value与label字段,默认值分别为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 项由field和order("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支持eq、ne、lt、gt、contains、in、between、null等一整套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可调用NotificationProvider的open方法展示成功通知,可用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挂载时,它会向liveProvider的subscribe方法传递channel、resource等参数,用于订阅实时更新。
liveMode:决定收到相关 live 事件后是否自动更新数据,"auto"自动更新,"manual"手动处理:
useSelect({ liveMode: "auto", });onLiveEvent:订阅到新事件时的回调函数:
useSelect({ onLiveEvent: (event) => { console.log(event); }, });liveParams:传递给liveProvider的subscribe方法的附加参数。
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最常见的实战场景是配合useForm和Create/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> ); };要点:
useForm的getInputProps("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?
使用optionLabel与optionValue,默认值分别是title和id。例如改为name与categoryId:
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>上即可。
六、返回值与类型参数速查
返回值
| 属性 | 说明 | 类型 |
|---|---|---|
selectProps | Mantine Select 的 props | SelectPropsType |
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 | 查询函数返回的数据类型,继承BaseRecord | BaseRecord |
TError | 自定义错误类型,继承HttpError | HttpError |
TData | select函数返回的数据类型;不指定时默认取TQueryFnData | TQueryFnData |
七、更进一步
- 想亲手运行
useSelect的完整示例,可查看仓库中的 base-mantine 示例项目,其中包含配套的create、edit、list、show页面。 - 深入理解底层数据获取:阅读 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 目录 下的
useForm、useDrawerForm等文档。
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考