Refine useSelect Hook 完全指南:无头 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 核心包(@refinedev/core)提供的无头(headless)数据 Hook,用于为任意select类组件绑定后端数据。本文以 useSelect 官方文档 为骨架,结合 packages/core/src/hooks/useSelect/index.ts 源码与 index.spec.ts 测试,系统讲解其全部配置属性、返回值、常见实战模式与底层实现原理。读完本文,你将能够用useSelect快速搭建分类选择、远程搜索(Autocomplete)、服务端排序/筛选、默认值回填以及实时更新的下拉框,并理解它内部如何调用useList、useMany与 TanStack Query。
useSelect 是什么
useSelect用于管理任意select类组件——无论是原生 HTML<select>标签、React Select 还是其他自定义选择器。由于它被设计为 headless,因此只负责数据获取与选项生成,UI 完全由你掌控。
这个 Hook 底层通过useList来获取数据,也就是说:
- 数据请求最终走
dataProvider的getList方法; - 排序(
sorters)、筛选(filters)、分页(pagination)等参数会原样透传给getList; - 所有查询由 TanStack Query 管理,天然支持缓存、重试、加载态等能力。
useSelect对应的 UI 库衍生版本:
- Ant Design Select(Ant Design 用户)— 文档 — 示例
- Material UI Autocomplete(Material UI 用户)— 文档
- Mantine Select(Mantine 用户)— 文档
如果你在使用上述 UI 库,可直接使用其封装版本,获得开箱即用的组件;useSelect本身则面向无头(headless)场景。
快速上手:最小可用示例
最基础的用法只需传入resource,然后从返回值中取options渲染即可:
import { useSelect } from "@refinedev/core"; interface ICategory { id: number; title: string; } const Categories: React.FC = () => { const { options } = useSelect<ICategory>({ resource: "categories", }); return ( <label> Select a category: <select> {options?.map((option) => ( <option key={option.value} value={option.value}> {option.label} </option> ))} </select> </label> ); };这正是文档中 基础用法实时预览 所演示的形态:Hook 内部根据getList返回的记录数组,按optionLabel(默认"title")与optionValue(默认"id")生成{ label, value }选项。测试 index.spec.ts 验证了这一行为:默认情况下,options由记录的title作为label、id作为value组装而成。
Properties:完整配置指南
resource(必填)
resource会经由useList作为参数传给dataProvider的getList方法,通常对应 API 端点路径,具体含义取决于你的getList实现:
useSelect({ resource: "categories", });如果存在同名资源,可以传identifier而不是资源name。它只作为资源的主匹配键,数据提供器的方法仍然使用在<Refine/>组件中定义的资源name。可参考identifier相关文档 与 创建 data provider。
optionLabel 与 optionValue
用于自定义选项的value与label,默认值分别为optionLabel = "title"、optionValue = "id":
useSelect<ICategory>({ resource: "products", optionLabel: "name", optionValue: "productId", });两个属性都支持 lodashget风格的嵌套路径访问:
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, });源码层面,index.ts 中的getOptionLabel/getOptionValue会判断类型:字符串走lodash/get路径取值,函数则直接调用。测试用例也覆盖了嵌套optionLabel: "nested.title"的场景(index.spec.ts)。
searchField
指定onSearch函数将按哪个字段搜索:
const { onSearch } = useSelect({ searchField: "name" }); onSearch("John"); // 按 `name` 字段、值为 John 搜索默认逻辑(见 index.ts):
- 若
optionLabel是字符串,默认用optionLabel的值; - 否则默认用
"title"字段。
// optionLabel 为字符串时 const { onSearch } = useSelect({ optionLabel: "name" }); onSearch("John"); // 按 name 字段搜索 // optionLabel 为函数时 const { onSearch } = useSelect({ optionLabel: (item) => `${item.id} - ${item.name}`, }); onSearch("John"); // 按 title 字段搜索sorters
控制选项的展示顺序,sorters会经useList传给getList,用于向 API 发送排序参数:
useSelect({ sorters: [ { field: "title", order: "asc", }, ], });文档配套的 排序实时预览 演示了通过按钮在asc/desc之间切换、选项即时按标题重排的完整交互。排序结构遵循CrudSorting接口。
filters
通过筛选来控制显示哪些选项。filters同样会经useList传给getList,作为筛选参数发送给 API:
useSelect({ filter: [ { field: "isActive", operator: "eq", value: true, }, ], });注意:属性名是
filters(复数),上文示例沿用了原文档的写法,实际使用时请使用filters。
筛选结构遵循CrudFilters接口。此外,useSelect内部会把filters与onSearch产生的搜索条件合并后一起传给useList(见 index.ts)。
defaultValue
让某些选项默认被选中,并额外向选项数组追加对应数据。当select数据量很大、需要分页时,defaultValue可能不在当前可见选项中,从而破坏select组件。为了避免这种情况,Hook 会额外发起一次useMany查询,把defaultValue对应的记录取回并追加到当前选项数组中。
useSelect({ defaultValue: 1, // 或 [1, 2] });defaultValue既可以是单个值,也可以是数组。源码中通过Array.isArray将其统一规整为数组(index.ts),并构造useMany查询(index.ts)。默认值实时预览 演示了defaultValue: 3时选项默认被选中的效果。
selectedOptionsOrder
控制selectedOptions(即defaultValue对应的选项)在最终options中的排序位置:
"in-place":默认值选项排在最底部(默认行为);"selected-first":默认值选项排在最顶部。
useSelect({ defaultValue: 1, // 或 [1, 2] selectedOptionsOrder: "selected-first", // in-place | selected-first });源码中通过uniqBy(..., "value")对两份选项做合并去重,再按该顺序拼接(index.ts)。这与 useMany 文档 的查询结果相关。
debounce
为onSearch函数增加防抖延迟(单位毫秒),避免每次按键都触发请求:
useSelect({ resource: "categories", debounce: 500, });源码中debounce默认值为300毫秒(index.ts),内部使用 lodash 的debounce包装onSearch(index.ts),并将用户传入的onSearch通过ref保存以避免闭包过期(index.ts)。
queryOptions
用于向 TanStack Query 的useQuery传递额外选项,例如重试次数:
useSelect({ queryOptions: { retry: 3, }, });它作用于主列表查询(getList),其类型为MakeOptional<UseQueryOptions<GetListResponse<TQueryFnData>, TError, GetListResponse<TData>>, "queryKey" | "queryFn">(index.ts),即 queryKey 与 queryFn 由 Refine 内部接管,其余 TanStack Query 配置均可透传。
pagination
分页参数,会作为参数传给getList,用于向 API 发送分页查询参数:
useSelect({ pagination: { currentPage: 2, }, });currentPage:指定页码;pageSize:指定每页条数:
useSelect({ pagination: { pageSize: 20, }, });mode:决定是否使用服务端分页,取值为"off"、"client"或"server":
useSelect({ pagination: { mode: "off", }, });源码中useSelect向useList传分页时,pageSize默认取10(index.ts)。
defaultValueQueryOptions
当设置了defaultValue时,Hook 会调用useMany查询所选记录。通过该属性可以自定义这次查询的选项;如果不传,则复用queryOptions中的值(index.ts):
const { options } = useSelect({ resource: "categories", defaultValueQueryOptions: { onSuccess: (data) => { console.log("triggers when on query return on success"); }, }, });其类型为MakeOptional<UseQueryOptions<GetManyResponse<TQueryFnData>, TError, GetManyResponse<TData>>, "queryKey" | "queryFn">(index.ts)。
onSearch
用于对选项做远程搜索(Autocomplete),返回一个设置搜索值的函数:
const { options, onSearch } = useSelect<ICategory>({ resource: "categories", onSearch: (value) => [ { field: "title", operator: "contains", value, }, ], }); // 在输入框的 onChange 中调用 <input onChange={(e) => onSearch(e.target.value)} />文档配套的 onSearch 实时预览 展示了完整的搜索交互。实现要点:
- 如果不传自定义
onSearch,Hook 会基于searchField自动生成{ field: searchField, operator: "contains", value }筛选条件(index.ts); - 如果传入自定义
onSearch,其返回值会覆盖现有filters(index.ts); - HTML 原生
select不原生支持 Autocomplete,如需该能力,可配合 React Select 或 use-select 之类的库使用; - 搜索条件结构遵循
CrudFilters接口。
meta
meta是一个特殊属性,用于向 data provider 方法传递额外信息,常见用途包括:
- 针对特定用例自定义 data provider 方法;
- 使用纯 JavaScript 对象(JSON)生成 GraphQL 查询。
下面示例把headers放在meta中传给create/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 }; }, //... };源码中meta会与useMeta解析出的全局 meta 合并(combinedMeta),并同时传给useMany与useList(index.ts)。更详细的说明见 General Concepts 文档中的 meta 概念。
dataProviderName
当存在多个 data provider 时,用它指定使用哪一个:
useSelect({ dataProviderName: "second-data-provider", });默认值为"default"(index.ts)。适合不同资源挂在不同数据源的场景。
successNotification / errorNotification
需要
NotificationProvider才能生效。
数据获取成功时,useSelect可以调用NotificationProvider的open方法展示成功通知,并可自定义其内容:
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决定收到相关实时事件后是否自动更新数据:"auto"自动更新,"manual"需要手动处理。可用于在应用中实时更新并展示数据:
useSelect({ liveMode: "auto", });onLiveEvent是订阅到新事件时的回调函数:
useSelect({ onLiveEvent: (event) => { console.log(event); }, });liveParams用于向 liveProvider 的subscribe方法传递参数。
值得注意的是,文档「Realtime Updates」一节指出:useSelect挂载时,会调用liveProvider的subscribe方法,并携带channel、resource等参数,以便订阅实时更新。同时defaultValue对应的useMany查询被显式设置为liveMode: "off"(index.ts),即默认值回填查询不参与实时订阅。
overtimeOptions
用于请求超时的加载指示。interval为毫秒级时间间隔,onInterval为每个间隔触发的回调。Hook 返回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>; }源码中通过useLoadingOvertime实现,其isLoading同时监听主列表查询与默认值查询的isFetching状态(index.ts)。
返回值
useSelect返回以下值:
| 属性 | 说明 | 类型 |
|---|---|---|
options | 生成的可用选项 | { label: string; value: string }[] |
query | 主列表查询结果 | QueryObserverResult<{ data: TData; error: TError }> |
defaultValueQuery | defaultValue对应记录的查询结果 | QueryObserverResult<{ data: TData; error: TError }> |
onSearch | 设置搜索值的函数 | (value: string) => void |
overtime | 超时加载信息 | { elapsedTime?: number } |
返回类型定义见 index.ts。options是options与selectedOptions按value去重合并后的结果(index.ts)。
类型参数
| 类型参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
TQueryFnData | 查询函数返回的数据类型,需继承BaseRecord | BaseRecord | BaseRecord |
TError | 自定义错误对象,需继承HttpError | HttpError | HttpError |
TData | select函数返回的数据类型,需继承BaseRecord;未指定时默认使用TQueryFnData的值 | BaseRecord | TQueryFnData |
各接口定义见 interface-references(BaseRecord、HttpError)。
常见问题(FAQ)
如何不分页获取全部数据?
将pagination.mode设为"off":
useSelect({ pagination: { mode: "off", }, });注意:data provider 必须实现对该模式的支持才能生效。
如何为选项添加搜索(Autocomplete)?
使用onSearch,它用于设置搜索值。简单示例如上文的 onSearch 一节所示,实时预览 展示了输入框与下拉框联动的完整效果。
如何确保defaultValue出现在选项中?
当手头只有id、但希望它在选择框中显示为已选中时,Hook 会通过useMany发起请求取回数据并标记为已选中(实时预览)。
如何修改选项的label与value?
使用optionLabel与optionValue,默认值分别为optionLabel="title"、optionValue="id"。要改为name与categoryId:
useSelect({ optionLabel: "name", optionValue: "categoryId", });可以手动创建选项吗?
当仅靠optionLabel与optionValue不够用时,可以直接用query返回值手动构建:
const { query } = useSelect({ resource: "categories", }); const options = query.data?.data.map((item) => ({ label: item.name, value: item.id, })); return ( <select> {options?.map((option) => ( <option key={option.value} value={option.value}> {option.label} </option> ))} </select> );源码实现剖析:useSelect 内部如何工作
理解底层实现有助于你更准确地使用它。核心实现位于 packages/core/src/hooks/useSelect/index.ts,关键链路如下:
- 资源解析:通过
useResourceParams将传入的resource解析为{ resource, identifier }(index.ts),列表查询使用identifier,默认值查询使用identifier ?? resource.name。 - meta 合并:
useMeta会把全局 meta 与传入的meta合并为combinedMeta(index.ts)。 - 两条数据链路:
- 主列表:
useList({ resource: identifier, sorters, filters: filters.concat(search), pagination, queryOptions, ... })(index.ts),search即onSearch产生的筛选条件; - 默认值:仅当
defaultValue非空时启用useMany查询(index.ts)。
- 主列表:
- 选项组装:两条查询的
onSuccess分别把记录映射为{ label, value }存入options与selectedOptions(index.ts),最终按selectedOptionsOrder拼接并经uniqBy("value")去重(index.ts)。 - 搜索防抖:
onSearch由 lodashdebounce包装,默认 300ms(index.ts)。
因此,useSelect本质上是一个“把getList结果转成下拉选项 + 用getMany补充默认选中项”的组合型数据 Hook,你完全可以把它当作useList+useMany的便捷封装来理解,并在需要时通过query返回值直接访问底层查询状态。
完整的可运行示例位于 examples/core-use-select,与 文档中的 CodeSandbox 示例 对应,可用于本地验证上述全部配置。
相关阅读
- useList Hook —
useSelect的底层数据来源 - useMany Hook —
defaultValue回填查询 - 创建 data provider —
getList/getMany的实现约定 - Live / Realtime —
liveMode与onLiveEvent详解 - Notification Provider — 成功/失败通知配置
- General Concepts:meta 概念 —
meta的完整语义 - 接口引用 —
BaseRecord、HttpError、CrudSorting、CrudFilters等类型定义 - Ant Design useSelect / Material UI useAutoComplete / Mantine useSelect — UI 库封装版本
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考