输入类 · Select
选择器
用户可以通过 Select 选择器从一个选项集合中去选中一个或多个选项,并呈现最终选择结果

代码演示

如何引入

注意
Select的直接子元素必须为 Option 或者 OptGroup,不允许为其他Element

基本使用

每个 Option 标签都必须声明 value 属性,Option 的 children 或 label 将会被渲染至下拉列表中

以数组形式传入 Option

可以直接通过optionList传入一个对象数组,每个对象必须包含 value/label 属性(当然其他属性也可以通过此方式传入)

多选

自 v2.28后,select 的选择器会自带 maxHeight 270,内容超出后可以通过垂直滚动查看。
配置multiple属性,可以支持多选
配置 maxTagCount 可以限制已选项展示的数量,超出部分将以+N 的方式展示
配置 ellipsisTrigger (>= v2.28.0) 对溢出部分的 tag 做自适应处理,当宽度不足时,最后一个tag内容作截断处理。开启该功能后会有一定性能损耗,不推荐在大表单场景下使用
配置 expandRestTagsOnClick (>= v2.28.0) 可以在设置 maxTagCount 情况下通过点击展示全剩余的tag
使用 showRestTagsPopover (>= v2.22.0) 可以设置在超出 maxTagCount 后,hover +N 是否显示 Popover,默认为 false。并且,还可以在 restTagsPopoverProps 属性中配置 Popover。
配置 max 属性可限制最大可选的数量,超出最大限制数量后无法选中,同时会触发onExceed回调

分组

分组功能 v0.31.0 后提供
用 OptGroup 进行分组(分组功能仅支持通过 jsx 方式声明 children 使用,不支持 optionList 方式传入)
注意
1. OptGroup必须为Select的直接子元素,不允许有Fragment或DIV等其他元素阻隔
2. 若Select的children需要动态更新,OptGroup上的key也需要进行更新,否则Select无法识别

不同尺寸

通过 Size 控制选择器的大小尺寸: small / default / large

不同校验状态样式

validateStatus: default / warning / error
仅影响背景颜色等样式表现

配置前缀、后缀、清除按钮

  • 可以通过prefix传入选择框前缀,通过suffix传入选择框后缀,可以为文本或者 ReactNode
    当 prefix、suffix 传入的内容为文本或者 Icon 时,会自动带上左右间隔,若为自定义 ReactNode,则左右间隔为 0
  • 通过showClear控制清除按钮是否展示
  • 通过showArrow控制右侧下拉箭头是否展示

内嵌标签

通过设置insetLabel,你可以给 Select 设置 label,可以传入 string 或者 ReactNode
当传入类型为 ReactNode 时,注意要自行处理 label 与文本之间的间隔

在顶部/底部渲染附加项

我们在弹出层顶部、底部分别预留了插槽,当你需要在弹出层中添加自定义 node 时
可以通过innerBottomSlot或者outerBottomSlot传入,自定义 node 将会被渲染在弹出层底部;可以通过innerTopSlot或者outerTopSlot传入,自定义 node 将会被渲染在弹出层顶部。
  • innerTopSlotinnerBottomSlot将会被渲染在 optionList 内部,当滚动到 optionList 顶部/底部时展现
  • outerTopSlotouterBottomSlot将会被渲染为与 optionList 平级,无论 optionList 是否滚动,都会始终展现
通过 outerTopSlot 将内容插入顶部插槽

受控组件

传入 value 时 Select 为受控组件,所选中的值完全由 value 决定。

动态修改 Options

如果需要动态更新 Options,应该使用受控的 value

联动

使用受控 value,实现不同 Select 之间的联动。如果是带有层级关系的复杂联动建议直接使用Cascader组件

开启搜索

filter 置为 true,开启搜索能力。默认搜索策略将为 input 输入值与 option 的 label 值进行 include 对比
默认情况下,多选选中后会自动清空搜索关键字。若你希望保留,可以通过 autoClearSearchValue 设为 false 关闭默认行为(v2.3 后提供)

搜索框位置

默认搜索框展示于 Select 的 Trigger 触发器上。通过 searchPosition 可以指定不同的位置,可选 dropdowntrigger。 在 v2.61.0后提供 若希望定制位于 dropdown 中的 Input 搜索框的 placeholder,可以通过 searchPlaceholder 控制
searchPosition 值为 trigger,当showClear=true 时,点击Trigger区域的清空按钮,将同时清空已选项以及搜索框中的文本
searchPosition 值为 dropdown,当showClear=true 时,点击Trigger区域清空按钮,仅清空已选项。点击搜索框中的清空按钮,仅清空搜索文本

远程搜索

带有远程搜索,防抖请求,加载状态的多选示例
通过filter开启搜索能力
remote设置为 true 关闭对当前数据的筛选过滤 通过动态更新optionList更新下拉菜单中的备选项
使用受控的 value 属性

自定义搜索逻辑

可以将 filter 置为自定义函数,定制你想要的搜索策略
如下例子,选项 label 值都是大写,默认的检索策略是字符串 include 对比,会区分大小写。
通过传入自定义 filter 函数,检索时输入小写字母也能搜到相应内容。

自定义已选项标签渲染

默认情况下,选中选项后会将 option.label 或 option.children 的内容回填到选择框中
但你可以通过 renderSelectedItem 自定义选择框中已选项标签的渲染结构
  • 单选时 renderSelectedItem(optionNode:object) => content:ReactNode
  • 多选时 renderSelectedItem(optionNode:object, { index:number, onClose:function }) => { isRenderInTag:bool, content:ReactNode }
    • isRenderInTag 为 true 时,会自动将 content 包裹在 Tag 中渲染(带有背景色以及关闭按钮)
    • isRenderInTag 为 false 时,将直接渲染返回的 content

自定义弹出层样式

你可以通过 dropdownClassName、dropdownStyle 控制弹出层的样式
例如当自定义弹出层的宽度时,可以通过 dropdownStyle 传入 width

获取选项的其他属性

默认情况下onChange只能拿到 value,如果需要拿选中节点的其他属性,可以使用onChangeWithObject属性
此时onChange函数的入参将会是 object,包含 option 的各种属性,例如 onChange({ value, label, ...rest })
注意
当 onChangeWithObject 置为 true 时,`defaultValue`/`Value`也应为 object,且须带有`value`、`label` key

创建条目

设置allowCreate,可以创建并选中选项中不存在的条目
允许通过 renderCreateItem 自定义创建标签时的内容显示(通过返回 ReactNode,注意你需要自定义样式),该函数默认值为 (input, isFocus, style) => '创建' + input
可以配合defaultActiveFirstOption属性使用,自动选中第一项,当输入完内容直接回车时,可立即创建
注意
当开启allowCreate后,不会再响应对Children或者optionList的更新

虚拟化

传入virtualize时开启列表虚拟化,用于大量 Option 节点的情况优化性能
virtualize 是一个包含下列值的对象:
  • height: Option 列表高度值,默认 270 (v2.20.8 前为 300)
  • width: Option 列表宽度值,默认 100%
  • itemSize: 每行 Option 的高度,必传
注意事项
Semi Select virtualize 功能是基于 react-window 的封装,虚拟化列表默认会被包裹在 `will-change: transform` 的 div 内部。 在某些浏览器(例如 Chrome),某些特定的屏幕尺寸下,屏幕物理像素尺寸与浏览器处理的像素无法对齐时,会自动开启抗锯齿。从而导致虚拟列表中的文本字体可能会在特定场景下存在模糊的情况。 will-change 对于复杂元素的渲染会有性能改善,所以我们默认不会对 react-window的样式进行覆盖。如果你希望关闭这个效果,可以通过自行覆盖 CSS,将 will-change 设置为 unset 解决

自定义触发器

如果 Select 默认的触发器样式满足不了你的需求,可以用triggerRender自定义选择框的展示
如果想保留搜索筛选能力,又不希望自己渲染 Input 相关的结构,可以同时通过 searchPosition='dropdown',将默认的搜索框置于下拉列表中
triggerRender 入参如下
下例是更复杂的例子:复用了 TagInput 拖拽排序能力,通过 triggerRender 为 Select 增加排序

自定义候选项渲染

  • 简单的自定义:通过 Option 的 label 属性或者 children 传入 ReactNode,你可以控制候选项的渲染,此时内容会自动带上内边距、背景色等样式
  • 完全自定义:通过传入renderOptionItem,你可以完全接管列表中候选项的渲染,并且从回调入参中,获取到相关的状态值。实现更高自由度的结构渲染
    注意事项:
    1. props 传入的 style 需在 wrapper dom 上进行消费,否则在虚拟化场景下会无法正常使用
    2. props 传入的 className、onMouseEnter 需在 wrapper dom 上进行消费,否则上下键盘操作时显示会有问题
    3. 选中(selected)、聚焦(focused)、禁用(disabled)等状态的样式需自行加上,你可以从 props 中获取到相对的 boolean 值
    4. 如果你的自定义 item 为 Select.Option,需要将 renderProps.onClick 透传给 Option 的 onSelect prop

API 参考

Select Props

属性说明类型默认值版本
allowCreate是否允许用户创建新条目,需配合 filter 使用。该项为true时不再响应 optionList的变更booleanfalse
arrowIcon自定义右侧下拉箭头 Icon,当 showClear 开关打开且当前有选中值时,hover 会优先显示 clear iconReactNode
autoAdjustOverflow浮层被遮挡时是否自动调整方向(暂时仅支持竖直方向,且插入的父级为 body)booleantrue
autoClearSearchValue选中选项后,是否自动清空搜索关键字,当 mutilple、filter 都开启时生效booleantrue2.3.0
autoFocus初始渲染时是否自动 focusbooleanfalse
borderless无边框模式 >=2.33.0boolean
className类名string
clearIcon可用于自定义清除按钮, showClear为true时有效ReactNode2.25.0
clickToHide已展开时,点击选择框是否自动收起下拉列表booleanfalse
defaultValue初始选中的值string|number|array
defaultOpen是否默认展开下拉列表booleanfalse
disabled是否禁用booleanfalse
defaultActiveFirstOption是否默认高亮第一个选项(按回车可直接选中)
v2.17.0 之后默认值从 false 变为 true
booleantrue
dropdownClassName弹出层的 classNamestring
dropdownMatchSelectWidth下拉菜单最小宽度是否等于 Selectbooleantrue
dropdownStyle弹出层的样式object
dropdownMargin弹出层计算溢出时的增加的冗余值,详见issue#549,作用同 Tooltip marginobject|number2.25.0
emptyContent无结果时展示的内容。设为 null 时,下拉列表将不展示string|ReactNode
ellipsisTrigger当 maxTagCount 存在且为多选时,是否对溢出部分的 tag 做自适应处理(当宽度不足时,最后一个tag内容作截断处理)。开启该功能后会有一定性能损耗,不推荐在大表单场景下使用booleanfalse2.28.0
expandRestTagsOnClick当maxTagCount存在且为多选时,select 在面板打开状态下是否展开多余的 Tagbooleanfalse2.28.0
filter是否可搜索,默认为 false。传入 true 时,代表开启搜索并采用默认过滤策略(label 是否与 sugInput 匹配),传入值为函数时,会接收 sugInput, option 两个参数,当 option 符合筛选条件应返回 true,否则返回 falseboolean |function(sugInput, option)false
getPopupContainer指定父级 DOM,弹层将会渲染至该 DOM 中,自定义需要设置 position: relative 这会改变浮层 DOM 树位置,但不会改变视图渲染位置。function():HTMLElement() => document.body
inputPropsfilter 为 true 时, input 输入框的额外配置参数,具体可配置属性请参考 Input 组件(注意:请不要传入 value、ref、onChange、onFocus,否则会覆盖 Select 相关回调,影响组件行为)object2.2.0
innerTopSlot渲染在弹出层顶部,在 optionList 内部的自定义 slotReactNode
innerBottomSlot渲染在弹出层底部,在 optionList 内部的自定义 slotReactNode
insetLabel同上,与 prefix 区别是 fontWeight 更大ReactNode
loading下拉列表是否展示加载动画booleanfalse
maxTagCount多选模式下,已选项超出 maxTagCount 时,后续选项会被渲染成+N 的形式number
max最多可选几项,仅在多选模式下生效number
maxHeight下拉菜单中 optionList 的最大高度string|number270
multiple是否多选booleanfalse
outerTopSlot渲染在弹出层顶部,与 optionList 平级的自定义 slotReactNode
outerBottomSlot渲染在弹出层底部,与 optionList 平级的自定义 slotReactNode
optionList可以通过该属性传入 Option,请确保数组内每个元素都具备 label、value 属性array([{value, label}])
placeholder选择框默认文字ReactNode
position菜单展开的位置,可选项同 Tooltip positionstring'bottomLeft'
prefix选择框的前缀标签ReactNode
preventScroll指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法boolean
renderCreateItemallowCreate 为 true 时,可自定义创建标签的渲染。与虚拟化结合使用时,必须将第三个参数style传入自定义DOM中消费(v2.44.1后提供)function(inputValue:string, isFocus: boolean, style: object)inputValue => '创建' + inputValue
renderSelectedItem通过 renderSelectedItem 自定义选择框中已选项标签的渲染function(option)
renderOptionItem通过 renderOptionItem 完全自定义下拉列表中候选项的渲染function(props) 入参详见 Demo
restTagsPopoverPropsPopover 的配置属性,可以控制 position、zIndex、trigger 等,具体参考PopoverPopoverProps{}2.22.0
remote是否开启远程搜索,当 remote 为 true 时,input 内容改变后不会进行本地筛选匹配booleanfalse
searchPositionfilter开启时,搜索框的位置,默认在 trigger中,可以通过设为 'dropdown' 将搜索框置于下拉列表顶部。搭配 triggerRender 使用可以实现更高自由度的交互string'trigger'2.61.0
size大小,可选值 default/small/largestring'default'
style样式object
stopPropagation是否阻止浮层上的点击事件冒泡booleantrue
suffix选择框的后缀标签ReactNode
showClear是否展示清除按钮booleanfalse
showArrow是否展示下拉箭头booleantrue
showRestTagsPopover当超过 maxTagCount,hover 到 +N 时,是否通过 Popover 显示剩余内容booleanfalse2.22.0
spacing浮层与选择器的距离number4
triggerRender自定义触发器渲染function
value当前选中的的值,传入该值时将作为受控组件,配合 onChange 使用string|number|array
validateStatus校验结果,可选warningerrordefault(只影响样式背景色)string'default'
virtualize列表虚拟化,用于大量节点的情况优化性能表现,由 height, width, itemSize 组成object
zIndex弹层的 zIndexnumber1030
onBlur失去焦点时的回调function(event)
onChange变化时回调函数function(value:string|number|array)
onCreateallowCreate 为 true,创建备选项时的回调function(option)
onClear清除按钮的回调function
onChangeWithObject是否将选中项 option 的其他属性作为回调。设为 true 时,onChange 的入参类型会从 string 变为 object: { value, label, ...rest }booleanfalse
onDropdownVisibleChange下拉菜单展开/收起时的回调function(visible:boolean)
onListScroll候选项列表滚动时的回调function(e)
onSearchinput 输入框内容发生改变时回调函数,第二个参数于 v2.31 后提供function(sugInput:string, e: ReactEvent)
onSelect被选中时的回调function(value, option)
onDeselect取消选中时的回调,仅在多选时有效function(value, option)
onExceed当试图选择数超出 max 限制时的回调,仅在多选时生效
入参在 v1.16.0 后提供
function(option)
onFocus获得焦点时的回调function(event)

Option Props


不同 Option 的 label 必须唯一,不允许重复
属性说明类型默认值
className样式类名string
disabled是否禁用booleanfalse
label展示的文本。渲染时优先取 label,若无则取 children、value,依次降级string|ReactNode
showTick被选中时,展示 √ 的 Iconbooleantrue
style样式object
value属性值string|number

OptGroup Props


属性说明类型
className样式类名string
label展示的文本ReactNode
style样式object

Methods

绑定在组件实例上的方法,可以通过 ref 调用实现某些特殊交互
方法说明版本
close调用时可以手动关闭下拉列表
open调用时可以手动展开下拉列表
focus调用时可以手动聚焦
clearInput调用时可以手动清空 input 搜索框的值
deselectAll调用时可以手动清空所有已选项
selectAll调用时可以选中所有 Option
search(value: string, event: event)可通过 ref 调用该方法进行搜索,该搜索值会被置给 Inputv2.35.0

Accessibility

ARIA

  • Select trigger 的 role 为 combobox,弹出层的 role 为 listbox,可选项的 role 为 option
  • Select trigger 具有 aria-haspopup、aria-expanded、aria-controls 属性,表示 trigger 与弹出层的关系
  • 多选时,listbox aria-multiselectable 为 true,表示当前可以多选
  • Option 选中时,aria-selected 为 true;当 Option 禁用时,aria-disabled 为 true
  • 属性 aria-activedescendant 能够保证在朗读旁白时识别到当前的选择的 option(更多用法请参考Managing Focus in Composites Using aria-activedescendant)

键盘和焦点

不带 Filter 功能的 Select:
  • Select 聚焦后,键盘用户可以通过 上箭头下箭头Enter 键打开下拉菜单,并将焦点自动聚焦到下拉菜单中的第一个选项上(defaultActiveFirstOption 默认为 true)
  • 当下拉菜单打开时:
    • 使用 Esc 键或 Tab 键可以关闭菜单
    • 使用 上箭头下箭头 可以切换选项
    • 被聚焦的选项可以通过 Enter 键选中,并收起面板
  • 当焦点在下拉菜单中,且用户使用的 innerBottomSlotouterBottomSlot 属性的自定义 slot 中含有可交互元素时:
    • 可以使用 Tab 键切换到这些可交互元素上
    • 当焦点在自定义 slot 的首个可交互元素上时,使用 Shift + Tab ,焦点回到 Select 框上
带 Filter 功能的 Select:
  • Select 聚焦后,键盘用户可以通过 上箭头下箭头Enter 键打开下拉菜单。此时焦点仍然处于 Select 框,用户可以输入内容,同时也能使用 上箭头下箭头 切换选项
  • 当下拉菜单打开时:键盘交互与不带 Filter 功能的 Select 一致
  • 当焦点在 Select 框上,且用户使用的 innerBottomSlotouterBottomSlot 属性的自定义 slot 中含有可交互元素时:
    • 可以使用 Tab 键切换到这些可交互元素上
    • 当焦点在自定义 slot 的首个可交互元素上时,使用 Shift + Tab ,焦点回到 Select 框上

文案规范

  • 选择器标签
    • 用 1-3 个词描述需要用户所做的输入
    • 使用语句书写规范(首字母大写,其余小写)
    • 避免使用标点符号和介词(“the”, “an”, “a”)
    • 标签需是独立语句。不要让标签是前半句语句,选项是后半句语句。
    • 使用描述性语句,而不是指示性语句。如果选项需要更多解释,可以在选择框下使用帮助文本。
  • 选择器选项
    • 如果没有默认选项,就使用“Select”做占位文案
    • 选项要按首字母顺序或者其他有逻辑的排列顺序,使用户更好地找到选项
    • 使用语句书写规范(首字母大写,其余小写),避免在句尾使用逗号和分号
    • 清晰表达出选项所表示的选择目的

设计变量

FAQ

  • 为什么 Semi 的 Select 要求 label 必须唯一,而不是 value 必须唯一?
    • 首先,我们一定需要一个唯一标识符用来做选中的判断。几乎所有 UI 库,对 Select.Option 使用时,最低要求都只会要求传入 label、value 两个值,而不会再单独要求传入一个 key(过于繁琐)。Semi 延续了这个设定
    • 那么为什么在 Semi 中 用 label 而不是 value 呢?
      • 以 value 还是 label 作为唯一判断符,本质上是 用户直觉 vs 研发直觉 的取舍。以 value 为唯一判断比较符合工程师直觉,但站在用户视角来看,他们能看到的只有 label,对 value 基本上是无感知的。
      • label 是用户唯一能感知的内容。从交互的角度而言,如果出现两个或多个展示上一模一样的选项,对用户而言,他们看上去是一样的,无法进行区分。用户第一反应往往是重复了,这个系统是不是出 bug 了。其次如果两个 option 展示上一模一样,但选中的作用又不一样(例如一个 value 为 0,另一个为 1,他们的处理逻辑完全不同)的话,也会让用户非常困惑。在现实生活的线下实体表单里,基本不可能出现两个一模一样的选项。
      • 假如我们以 value 作为判断符,以下例子,用户点击了 A 进行选中,实际上却看到 A、B、C 都被同时选中了。同样也会非常困惑,第一反应也往往是系统出 bug 了。
      • label 唯一、value 重复,在日常使用中会更为常见。例如,一个根据 app 名称选择公司 id 的选择器,value 是 app 对应的公司 id,label 是 app 的名称。
    • 分组情况下,重复 label 并不会造成用户困惑为什么仍要求 label 必须唯一?
      • 选择面板打开情况下,确实不会造成用户使用上的困惑,但是选择面板收起后,重复 label 属于哪个分组对用户而言仍具有迷惑性。
    • 我的数据里确实就有多个 label 一样的选项,无法避免。这个交互能绕过吗?
      • 可以。我们不推荐向用户展示重复的 label option,但如果你确定你需要这么做,当你往 label 传入 ReactNode 类型时,可以绕过这个限制。
  • 可搜索的 Select,使用远程数据动态更新optionList,为什么在异步请求完成之前有时候会出现暂无数据?
    请检查是否设置了remote={true},不设置 remote 的情况下,默认会将 input 框输入值与当前的 optionList 进行一次对比筛选,如果无匹配时,就会显示暂无数据。
    可以通过设置 remote 为 true 关闭对本地当前数据的匹配筛选。
  • 使用 jsx 方式声明 Option,label 为 i18n 后的内容,切换 locale 后未能重新渲染
    • children jsx 方式声明 Options 时,由于是 ReactNode,不可能用 deepEqual 来做对比判断内容是否有更新(性能消耗过大),所以会收集 children ReactNode 的 key,当 key 不变时,就认为 Options 都没有发生变化,不会走重新收集数据的流程。你可以将 locale 也作为 Option key 的一部分。
    • 使用 optionList 方式传入,也可以解决问题。因为对于 object 形式传入,key 相对有限,Select 内部会使用 isEqual 来判断是否发生变化
  • 使用 jsx 方式声明 Option,动态切换 disabled 属性后未能重新渲染
    • 原因同上,你可以重新给 Option 设定不同的 key 值,或者使用 optionList 方式声明候选项
  • Select 会自动限制下拉菜单的宽度吗?
    • 会给 minWidth,但不会写死 width。如果有需要的话,可以自己通过 dropdownStyle 来添加。
  • 设置 allowCreate 后,动态更新 optionList 或者 children 不生效
    • allowCreate 主要用于本地创建的场景,开启该项后,相当于强接管了 optionList / children,不会再响应外部对这两类值的更新。
  • 为什么单选选择选项后没有触发 blur 事件?
    • 在 V2.17.0 前,Select 单选选择后,会触发 Select 的 blur 事件。
    • 在 V2.17.0 后,Select 增加了 A11y 支持,不会触发 Select 的 blur 事件。
      • 单选选择中,Select 浮层关闭,依然保持焦点在 trigger(此时可以通过 Enter 回车键再次打开 Select 浮层)
      • 无论单选或多选,按下 Esc,仅 Select 浮层关闭,trigger 保持焦点(此时可以通过 Enter 回车键再次打开 Select 浮层)