news 2026/9/11 4:02:24

Refine useSelect Hook 完全指南:无头 Select 数据绑定、搜索、默认值与实时更新

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Refine useSelect Hook 完全指南:无头 Select 数据绑定、搜索、默认值与实时更新

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)、服务端排序/筛选、默认值回填以及实时更新的下拉框,并理解它内部如何调用useListuseMany与 TanStack Query。

useSelect 是什么

useSelect用于管理任意select类组件——无论是原生 HTML<select>标签、React Select 还是其他自定义选择器。由于它被设计为 headless,因此只负责数据获取与选项生成,UI 完全由你掌控

这个 Hook 底层通过useList来获取数据,也就是说:

  • 数据请求最终走dataProvidergetList方法;
  • 排序(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作为labelid作为value组装而成。

Properties:完整配置指南

resource(必填)

resource会经由useList作为参数传给dataProvidergetList方法,通常对应 API 端点路径,具体含义取决于你的getList实现:

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

如果存在同名资源,可以传identifier而不是资源name。它只作为资源的主匹配键,数据提供器的方法仍然使用在<Refine/>组件中定义的资源name。可参考identifier相关文档 与 创建 data provider。

optionLabel 与 optionValue

用于自定义选项的valuelabel,默认值分别为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内部会把filtersonSearch产生的搜索条件合并后一起传给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", }, });

源码中useSelectuseList传分页时,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),并同时传给useManyuseList(index.ts)。更详细的说明见 General Concepts 文档中的 meta 概念。

dataProviderName

当存在多个 data provider 时,用它指定使用哪一个:

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

默认值为"default"(index.ts)。适合不同资源挂在不同数据源的场景。

successNotification / errorNotification

需要NotificationProvider才能生效。

数据获取成功时,useSelect可以调用NotificationProvideropen方法展示成功通知,并可自定义其内容:

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挂载时,会调用liveProvidersubscribe方法,并携带channelresource等参数,以便订阅实时更新。同时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 }>
defaultValueQuerydefaultValue对应记录的查询结果QueryObserverResult<{ data: TData; error: TError }>
onSearch设置搜索值的函数(value: string) => void
overtime超时加载信息{ elapsedTime?: number }

返回类型定义见 index.ts。optionsoptionsselectedOptionsvalue去重合并后的结果(index.ts)。

类型参数

类型参数说明类型默认值
TQueryFnData查询函数返回的数据类型,需继承BaseRecordBaseRecordBaseRecord
TError自定义错误对象,需继承HttpErrorHttpErrorHttpError
TDataselect函数返回的数据类型,需继承BaseRecord;未指定时默认使用TQueryFnData的值BaseRecordTQueryFnData

各接口定义见 interface-references(BaseRecordHttpError)。

常见问题(FAQ)

如何不分页获取全部数据?

pagination.mode设为"off"

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

注意:data provider 必须实现对该模式的支持才能生效。

如何为选项添加搜索(Autocomplete)?

使用onSearch,它用于设置搜索值。简单示例如上文的 onSearch 一节所示,实时预览 展示了输入框与下拉框联动的完整效果。

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

当手头只有id、但希望它在选择框中显示为已选中时,Hook 会通过useMany发起请求取回数据并标记为已选中(实时预览)。

如何修改选项的labelvalue

使用optionLabeloptionValue,默认值分别为optionLabel="title"optionValue="id"。要改为namecategoryId

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

可以手动创建选项吗?

当仅靠optionLabeloptionValue不够用时,可以直接用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,关键链路如下:

  1. 资源解析:通过useResourceParams将传入的resource解析为{ resource, identifier }(index.ts),列表查询使用identifier,默认值查询使用identifier ?? resource.name
  2. meta 合并useMeta会把全局 meta 与传入的meta合并为combinedMeta(index.ts)。
  3. 两条数据链路
    • 主列表:useList({ resource: identifier, sorters, filters: filters.concat(search), pagination, queryOptions, ... })(index.ts),searchonSearch产生的筛选条件;
    • 默认值:仅当defaultValue非空时启用useMany查询(index.ts)。
  4. 选项组装:两条查询的onSuccess分别把记录映射为{ label, value }存入optionsselectedOptions(index.ts),最终按selectedOptionsOrder拼接并经uniqBy("value")去重(index.ts)。
  5. 搜索防抖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 —liveModeonLiveEvent详解
  • Notification Provider — 成功/失败通知配置
  • General Concepts:meta 概念 —meta的完整语义
  • 接口引用 —BaseRecordHttpErrorCrudSortingCrudFilters等类型定义
  • 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 4:01:52

深度学习中的分数匹配:原理与PyTorch实战

1. 项目概述Score Matching&#xff08;分数匹配&#xff09;是近年来深度学习领域兴起的一种新型概率密度估计方法&#xff0c;它通过直接匹配数据分布的"分数"&#xff08;即对数概率密度的梯度&#xff09;来训练模型&#xff0c;避免了传统方法中计算归一化常数的…

作者头像 李华
网站建设 2026/9/11 4:00:39

AI Agent开发实战:从Python环境到生产级数字员工

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 4:00:30

精准护肤推荐系统:基于肤质与诉求的智能匹配技术

1. 项目概述&#xff1a;精准护肤推荐系统的核心价值 每次走进化妆品专柜&#xff0c;面对琳琅满目的瓶瓶罐罐时&#xff0c;你是否也感到无从下手&#xff1f;作为在美妆行业摸爬滚打十年的从业者&#xff0c;我见过太多人因为选错护肤品而导致皮肤问题加剧的案例。这套"…

作者头像 李华
网站建设 2026/9/11 4:00:04

ESP32实时语音链路:WebSocket+PCM实现低延迟AI对话

1. 项目概述&#xff1a;为什么“能对话”不等于“在对话”你拆开过市面上那些标榜“AI玩偶”的玩具吗&#xff1f;我拆过三款&#xff0c;从某国际大厂到两个国内新锐品牌。它们的共同点是&#xff1a;按下按钮&#xff0c;孩子说一句“你好”&#xff0c;玩偶停顿1.2秒&#…

作者头像 李华