Refine useSelect Hook 实战指南:将 Ant Design Select 与资源数据无缝绑定
2026/9/13 1:21:37 网站建设 项目流程

Refine useSelect Hook 实战指南:将 Ant Design 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 面向 Ant Design 提供的字段级 Hook,用于把一个资源(resource)中的记录直接转换成 Ant Design<Select>组件的选项(options)。它内部基于useList完成数据拉取,并自动处理加载态、搜索(含防抖)、排序、过滤与分页。读完本文,你将掌握useSelect的完整属性体系、返回值语义、与useForm的组合方式,以及它在 @refinedev/antd 源码 与 @refinedev/core 源码 中的真实实现原理。

本文以 documentation/docs/ui-integrations/ant-design/hooks/use-select/index.md 及其配套的可运行示例(_basic-usage-live-preview.md_on-search-live-preview.md_sort-live-preview.md_default-value-live-preview.md_crud-live-preview.md)为主体展开。

useSelect 是什么:定位与数据流

useSelect允许你在资源记录需要作为下拉选项时,管理 Ant Design 的<Select>组件。它把"取数据"这件事完全交给 Refine:

  • 主查询走useList(调用dataProvider.getList),用于拉取选项列表;
  • 当配置了defaultValue时,会额外走useMany(调用dataProvider.getMany),用于补齐"默认选中项"的数据。

用一句话概括:你只需要声明资源名,useSelect负责把数据变成{ label, value }形状的 options,并把这些 options 连同搜索、加载态一起打包成selectProps,直接展开到<Select>上即可

关于useList的更多细节,可参考 useList 文档。

快速上手:基础用法

先看一个完整的入门示例——在"创建文章"页面中,从categories资源加载分类下拉选项:

import { useSelect } from "@refinedev/antd"; import { Select } from "antd"; interface ICategory { id: number; title: string; } const PostCreate: React.FC = () => { const { selectProps } = useSelect<ICategory>({ resource: "categories", }); return ( <Select placeholder="Select a category" style={{ width: 300 }} {...selectProps} /> ); };

selectProps展开到<Select>上,即获得了:

  • options:由categories资源记录转换而成的{ label, value }[]
  • onSearch:内置的搜索回调(默认对title字段做contains过滤);
  • loading:数据拉取中的加载状态;
  • showSearch: truefilterOption: false:开启搜索框,但把过滤逻辑交给服务端(由onSearch触发的getList请求完成)。

这些默认行为在 packages/antd/src/hooks/fields/useSelect/index.ts 中可见一斑。

重要:useSelect 不管理选中值

useSelect主要面向数据获取(管理 options、loading、分页),不管理<Select>的受控状态(当前选中值)。如果独立使用<Select>,你需要自己用useState维护value/onChange;如果与 Ant Design Form 一起使用,则把<Select>放进Form.Item,由表单负责选中值。

核心属性配置详解

resource(必填)

resource会经由useList作为参数传给dataProvidergetList方法。它通常被当作 API 端点路径使用,但具体如何解释取决于你的getList实现(参见 创建 data provider 文档)。

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

如果存在多个同名资源,可以传入identifier来替代name作为资源的匹配键;data provider 方法仍使用<Refine />组件中定义的name工作。相关说明见<Refine />组件的identifier章节。

optionLabel 与 optionValue

用于自定义选项的valuelabel。默认值分别为optionLabel = "title"optionValue = "id"

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

这两个属性支持Object path 嵌套访问(lodash 风格,即get(item, path)):

const { options } = useSelect({ resource: "categories", optionLabel: "nested.title", optionValue: "nested.id", });

也支持传入函数,函数会收到item参数,便于拼接显示:

const { options } = useSelect({ optionLabel: (item) => `${item.firstName} ${item.lastName}`, optionValue: (item) => item.id, });

从源码看,core 层通过getOptionLabel/getOptionValue两个useCallback统一处理"字符串路径"与"函数"两种形态(见 packages/core/src/hooks/useSelect/index.ts)。

searchField

指定onSearch时按哪个字段进行搜索:

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

默认规则:当optionLabel是字符串时,沿用optionLabel的值;否则回退到title字段:

// optionLabel 为字符串时,搜索 name 字段 const { onSearch } = useSelect({ optionLabel: "name" }); onSearch("John"); // 按 name 搜索 // optionLabel 为函数时,回退到 title 字段 const { onSearch } = useSelect({ optionLabel: (item) => `${item.id} - ${item.name}`, }); onSearch("John"); // 按 title 搜索

sorters

控制选项的展示顺序,会经useList传给getList,最终以排序查询参数的形式发给 API:

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

你甚至可以把排序字段与顺序做成受控状态,实现"一键切换排序":

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

sorter的完整形态可参考 CrudSorting 接口文档。

filters

过滤展示的选项,同样会经useList传给getList

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

完整的过滤器形态见 CrudFilters 接口文档。

defaultValue:保证默认值出现在选项中

当选项数量很多、需要分页时,某个"默认选中值"可能不在当前可见列表中,导致<Select>显示异常。为此,useSelect会在配置defaultValue时额外发起一次useMany查询,把默认值对应的记录补充进 options。defaultValue可以是单个值,也可以是数组:

useSelect({ defaultValue: 1, // 或 [1, 2] });

:::info 注意defaultValue并不设置默认选中项,它只保证该值存在于选项中。要真正默认选中,请把值传给<Select>valueprop 或useForm

const form = useForm({ defaultValues: { category: { id: 1 }, // 默认选中值 }, }); const { selectProps } = useSelect({ resource: "categories", defaultValue: [1], // 确保默认值出现在选项中 });

:::

selectedOptionsOrder

控制defaultValue对应的"已选选项"在列表中的位置:

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

这背后依赖useMany查询,可参考 useMany 文档。

debounce

onSearch函数做防抖处理,单位为毫秒(core 层默认值为300):

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

queryOptions

透传给内部useQuery的额外选项,例如控制重试次数:

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

pagination

分页配置会经useList传给getList,用于发送分页查询参数。它支持以下子项:

currentPage:指定页码

useSelect({ pagination: { currentPage: 2, }, });

pageSize:每页条数

useSelect({ pagination: { pageSize: 20, }, });

mode"off""client""server",决定是否使用服务端分页

useSelect({ pagination: { mode: "off", }, });

值得注意的是,当pageSize未指定时,core 层默认使用10(见 packages/core/src/hooks/useSelect/index.ts)。

defaultValueQueryOptions

当传入defaultValue时,会调用useMany查询已选记录。defaultValueQueryOptions用来定制这次查询的选项;如果未传入defaultValue,则会回退使用queryOptions中的值:

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

onSearch:给 options 加上 Autocomplete

onSearch允许为选项加入 Autocomplete(自动补全)能力。它的签名是(value: string) => CrudFilter[],即"输入值 → 过滤条件":

import { useSelect } from "@refinedev/antd"; import { Select } from "antd"; interface ICategory { id: number; title: string; } const PostCreate: React.FC = () => { const { selectProps } = useSelect<ICategory>({ resource: "categories", onSearch: (value) => [ { field: "title", operator: "contains", value, }, ], }); return ( <Select placeholder="Select a category" style={{ width: 300 }} {...selectProps} /> ); };

注意:一旦使用onSearch,它会覆盖已有的filters(搜索条件会整体替换原过滤条件)。原因是 core 层在发起useList时执行filters.concat(search),而onSearch返回的数组会整体写入search状态(见 packages/core/src/hooks/useSelect/index.ts)。

客户端过滤:不请求服务端

如果你希望完全在客户端过滤选项,把onSearch显式传为undefined,并设置filterOptionoptionFilterProp

const { selectProps } = useSelect({ resource: "categories", }); <Select {...selectProps} onSearch={undefined} filterOption={true} optionFilterProp="label" // 或 "value" />;

meta

meta用于向 data provider 方法传递额外信息,典型用途包括:

  • 针对特定场景定制 data provider 方法;
  • 用纯 JavaScript 对象(JSON)生成 GraphQL 查询。

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

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

更系统的说明见 General Concepts 的 meta 概念章节。

dataProviderName

当项目配置了多个 data provider 时,用它指定使用哪一个:

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

successNotification 与 errorNotification

在数据成功/失败拉取后,useSelect可以调用NotificationProvideropen方法展示通知。这两个 prop 用于自定义通知内容(依赖 NotificationProvider):

useSelect({ successNotification: (data, values, resource) => { return { message: `${data.title} Successfully fetched.`, description: "Success with no errors", type: "success", }; }, });
useSelect({ errorNotification: (data, values, resource) => { return { message: `Something went wrong when getting ${data.id}`, description: "Error", type: "error", }; }, });

实时更新相关:liveMode / onLiveEvent / liveParams

以下属性依赖 LiveProvider:

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

useSelect挂载时,它会向subscribe方法传递channelresource等参数,从而实现实时订阅。

overtimeOptions:超时加载提示

当你希望在请求耗时过长时展示加载提示,可以传入overtimeOptionsinterval是毫秒级的时间间隔,onInterval是每个间隔触发的回调:

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>; }

overtime.elapsedTime表示已耗时毫秒数,请求完成后会变为undefined

返回值速查

返回值说明
selectProps可直接展开到<Select>的 Ant Design 属性(options、onSearch、loading、showSearch、filterOption)
query列表查询结果(useList对应的QueryObserverResult
defaultValueQuerydefaultValue记录的查询结果(useMany对应结果)
defaultValueQueryOnSuccess默认值查询成功时的回调
overtime超时加载属性,含elapsedTime

实战问答:高频场景速查

如何给 options 加搜索(Autocomplete)?

使用onSearch,它负责设置搜索值并触发服务端过滤。完整示例见上文 onSearch 一节,以及配套的_on-search-live-preview.md示例。

如何确保defaultValue出现在选项中?

有时我们只拿到id,却希望它在选择框中被显示为已选。useSelect会通过useMany拉取该记录数据并标记为已选(示例见 _default-value-live-preview.md):

const { selectProps } = useSelect<ICategory>({ resource: "categories", defaultValue: 11, });

如何修改 options 的 label 和 value?

使用optionLabeloptionValue,默认是titleid。要改成namecategoryId

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

可以手动创建 options 吗?

有时仅靠optionLabel/optionValue不够灵活,可以直接从query返回值手动构造 options:

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

如何与 CRUD 组件和 useForm 配合?

selectProps放进Form.Item,与useFormformPropssaveButtonProps组合即可(示例见 _crud-live-preview.md):

import { Create, useSelect, useForm } from "@refinedev/antd"; import { Form, Select } from "antd"; interface ICategory { id: number; title: string; } const PostCreate: React.FC = () => { const { formProps, saveButtonProps } = useForm<ICategory>(); const { selectProps } = useSelect<ICategory>({ resource: "categories", }); return ( <Create saveButtonProps={saveButtonProps}> <Form {...formProps} layout="vertical"> <Form.Item label="Category" placeholder="Select a category" name={["category", "id"]} rules={[ { required: true, }, ]} > <Select {...selectProps} /> </Form.Item> </Form> </Create> ); };

源码级原理剖析

第一层:@refinedev/antd 的轻量封装

packages/antd/src/hooks/fields/useSelect/index.ts 中的useSelect只是一个"适配层":它调用 core 层的useSelectCore拿到querydefaultValueQueryonSearchoptions,再组装成 Ant Design 的selectProps

return { selectProps: { options, onSearch, loading: defaultValueQuery.query.isFetching, showSearch: true, filterOption: false, }, query, defaultValueQuery: defaultValueQuery.query, };

可以看到加载态同时考虑了默认值查询的isFetching,并默认开启showSearch、关闭客户端filterOption(把过滤交给服务端搜索)。

第二层:@refinedev/core 的核心逻辑

packages/core/src/hooks/useSelect/index.ts 实现了真正的数据编排:

  • 两个查询并行useMany(拉取defaultValue记录)与useList(拉取选项列表)同时发起。useMany仅在defaultValues.length > 0enabled为 true 时启用(L286-L298);
  • 默认值归一化defaultValue统一转为数组defaultValues(L249-L251);
  • 搜索防抖onSearchlodash/debounce包裹,debounce默认300ms;若用户提供了onSearch函数则完全采用其返回的过滤数组,否则自动生成{ field: searchField, operator: "contains", value }条件(L343-L363);
  • 选项合并去重:通过uniqBy(..., "value")把主列表 options 与默认值 selectedOptions 合并,selectedOptionsOrder决定合并顺序(in-place时默认值在后,selected-first时在前)(L326-L335);
  • 标签解析getOptionLabel/getOptionValue使用lodash/get支持点分路径,也支持函数形态(L216-L236);
  • Overtime 计时useLoadingOvertime同时监听主查询与默认值查询的isFetching(L320-L324)。

测试佐证

useSelect的行为由 packages/antd/src/hooks/fields/useSelect/index.spec.ts 中的测试用例覆盖,涉及资源解析、optionLabel/optionValue、排序、过滤等场景,可作为自定义改造时的行为参照。

配套示例项目

仓库提供了两个可直接运行、对照学习的完整示例:

  • examples/field-antd-use-select-basic:useSelect基础用法(基本选项绑定、搜索、默认值等);
  • examples/field-antd-use-select-infinite:无限滚动加载示例,演示选项很多时如何结合分页滚动加载。

小结

useSelect是 Refine + Ant Design 组合下处理下拉选项的最高频 Hook 之一:它用一行resource声明替代了手写getList请求、状态管理与选项映射的重复劳动,并通过onSearchsortersfiltersdefaultValuepagination等属性覆盖了搜索、排序、过滤、默认值补齐、分页等几乎全部真实业务场景。理解其"useList拉选项 +useMany补默认值 + debounce 搜索 + uniqBy 合并"的底层实现(见 packages/core/src/hooks/useSelect/index.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),仅供参考

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

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

立即咨询