Select
Select 从 HeroUI v2 到 v3 的迁移指南。
完整的 API 参考、样式指南与高级示例请参阅 v3 Select 文档。本指南只关注从 HeroUI v2 的迁移。
结构变化
在 v2 中,Select 通过 prop 构成简单结构:
import { Select, SelectItem } from "@heroui/react";
export default function App() {
return (
<Select label="Select animal" placeholder="Choose one">
<SelectItem key="cat">Cat</SelectItem>
<SelectItem key="dog">Dog</SelectItem>
</Select>
);
}在 v3 中,Select 需要使用复合组件,并通过 ListBox 渲染菜单项:
import { Select, Label, ListBox } from "@heroui/react";
export default function App() {
return (
<Select placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>
);
}关键变化
1. 组件结构
v2: 简单的 Select,子节点为 SelectItem
v3: 复合组件(Select.Trigger、Select.Value、Select.Indicator、Select.Popover)与用于菜单项的 ListBox
2. 菜单项组件
v2: SelectItem、SelectSection
v3: ListBox.Item、ListBox.Section(分组标题使用 Header,分组之间使用 Separator)
3. Prop 变更
| v2 prop | v3 位置 | 说明 |
|---|---|---|
selectedKeys | value | 从 Set / 数组改为单个值或数组 |
onSelectionChange | onChange | 已重命名的事件处理函数 |
defaultSelectedKeys | defaultValue | 已重命名的 prop |
label | — | 使用 Label 组件 |
description | — | 使用 Description 组件 |
errorMessage | — | 使用 FieldError 组件 |
variant | variant | 仅保留 primary | secondary |
color | — | 已移除(请使用 Tailwind CSS) |
size | — | 已移除(请使用 Tailwind CSS) |
radius | — | 已移除(请使用 Tailwind CSS) |
classNames | — | 在各子组件上使用 className prop |
startContent | — | 直接自定义 Select.Trigger |
endContent | — | 直接自定义 Select.Trigger |
selectorIcon | — | 自定义 Select.Indicator 的 children |
isClearable | — | 手动实现清除按钮 |
renderValue | — | 使用 Select.Value 的渲染 prop |
labelPlacement | — | 标签始终在外部 |
isRequired | isRequired | 仍然可用 |
disabledKeys | disabledKeys | 仍然可用 |
isOpen | isOpen | 新增:受控地控制 Popover 的打开状态 |
defaultOpen | defaultOpen | 新增:非受控的默认打开状态 |
onOpenChange | onOpenChange | 新增:打开状态变化时触发 |
selectionMode | selectionMode | 仍然可用 |
disableAnimation | — | 已移除(动画机制已不同) |
popoverProps、listboxProps、scrollShadowProps | — | 直接在 Select.Popover、ListBox 等组件上传入 prop |
迁移示例
选择
import { useState } from "react";
{/* Single selection */}
const [singleValue, setSingleValue] = useState(new Set([]));
<Select
selectedKeys={singleValue}
onSelectionChange={setSingleValue}
label="Select animal"
>
<SelectItem key="cat">Cat</SelectItem>
<SelectItem key="dog">Dog</SelectItem>
</Select>
{/* Multiple selection */}
const [multiValue, setMultiValue] = useState(new Set([]));
<Select
selectionMode="multiple"
selectedKeys={multiValue}
onSelectionChange={setMultiValue}
label="Select animals"
>
<SelectItem key="cat">Cat</SelectItem>
<SelectItem key="dog">Dog</SelectItem>
</Select>import { useState } from "react";
import type { Key } from "@heroui/react";
{/* Single selection */}
const [singleValue, setSingleValue] = useState<Key | null>(null);
<Select value={singleValue} onChange={setSingleValue} placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>
{/* Multiple selection */}
const [multiValue, setMultiValue] = useState<Key[]>([]);
<Select
selectionMode="multiple"
value={multiValue}
onChange={setMultiValue}
placeholder="Choose multiple"
>
<Label>Select animals</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>表单校验
{/* With description */}
<Select
label="Select animal"
description="Choose your favorite"
placeholder="Choose one"
>
<SelectItem key="cat">Cat</SelectItem>
</Select>
{/* With validation */}
<Select
isInvalid
errorMessage="Please select an option"
label="Select animal"
>
<SelectItem key="cat">Cat</SelectItem>
</Select>import { Label, Description, FieldError } from "@heroui/react";
{/* With description */}
<Select placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Description>Choose your favorite</Description>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>
{/* With validation */}
<Select isInvalid placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
<FieldError>Please select an option</FieldError>
</Select>带分组
import { Select, SelectItem, SelectSection } from "@heroui/react";
<Select label="Select animal">
<SelectSection title="Mammals">
<SelectItem key="cat">Cat</SelectItem>
<SelectItem key="dog">Dog</SelectItem>
</SelectSection>
<SelectSection title="Birds">
<SelectItem key="eagle">Eagle</SelectItem>
<SelectItem key="parrot">Parrot</SelectItem>
</SelectSection>
</Select>import { Select, Label, ListBox, Header, Separator } from "@heroui/react";
<Select placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Section>
<Header>Mammals</Header>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox.Section>
<Separator />
<ListBox.Section>
<Header>Birds</Header>
<ListBox.Item id="eagle" textValue="Eagle">
Eagle
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="parrot" textValue="Parrot">
Parrot
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox.Section>
</ListBox>
</Select.Popover>
</Select>受控的打开状态
{/* v2 不支持受控的打开状态 */}
<Select label="Select animal">
<SelectItem key="cat">Cat</SelectItem>
<SelectItem key="dog">Dog</SelectItem>
</Select>import { useState } from "react";
import { Select, Label, ListBox } from "@heroui/react";
const [isOpen, setIsOpen] = useState(false);
<Select isOpen={isOpen} onOpenChange={setIsOpen} placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>禁用选项
import { Select, SelectItem } from "@heroui/react";
<Select label="Select animal" disabledKeys={["dog"]}>
<SelectItem key="cat">Cat</SelectItem>
<SelectItem key="dog">Dog</SelectItem>
<SelectItem key="parrot">Parrot</SelectItem>
</Select>import { Select, Label, ListBox } from "@heroui/react";
<Select disabledKeys={["dog"]} placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="parrot" textValue="Parrot">
Parrot
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>必填
import { Select, SelectItem } from "@heroui/react";
<Select isRequired label="Select animal">
<SelectItem key="cat">Cat</SelectItem>
<SelectItem key="dog">Dog</SelectItem>
</Select>import { Select, Label, ListBox } from "@heroui/react";
<Select isRequired placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>自定义指示器
<Select selectorIcon={<CustomIcon />} label="Select animal">
<SelectItem key="cat">Cat</SelectItem>
</Select><Select placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator>
<CustomIcon />
</Select.Indicator>
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>组件剖析
v3 Select 的结构如下:
Select (Root)
├── Label (optional)
├── Select.Trigger
│ ├── Select.Value
│ └── Select.Indicator
├── Description (optional)
├── Select.Popover
│ └── ListBox
│ ├── ListBox.Item
│ │ ├── Label (optional)
│ │ ├── Description (optional)
│ │ └── ListBox.ItemIndicator
│ ├── ListBox.Section (optional)
│ │ ├── Header (section title)
│ │ └── ListBox.Item
│ └── Separator (optional, between sections)
└── FieldError (optional)重要说明
菜单项标识
- v2: React 的
key同时用于列表协调与选择时的项标识。 - v3: 在
ListBox.Item上使用id(状态 / 焦点)与textValue(无障碍);列表协调仍使用 React 的key。
选择值的类型
- v2:
selectedKeys为Set<Key>或数组 - v3: 单选时
value为Key | null,多选时为Key[]
清除按钮
isClearable prop 已移除。若要实现清除按钮:
<Select value={value} onChange={setValue}>
<Select.Trigger>
<Select.Value />
{value && (
<button onClick={() => setValue(null)}>Clear</button>
)}
<Select.Indicator />
</Select.Trigger>
{/* ... */}
</Select>总结
- 组件结构:必须使用复合组件(
Select.Trigger、Select.Value、Select.Indicator、Select.Popover)。 - 菜单项组件:
SelectItem→ListBox.Item,SelectSection→ListBox.Section(分组标题用Header,分组之间用Separator)。 - 标签 / 描述 / 错误:使用独立组件,而不是对应 prop。
- 选择相关 prop:
selectedKeys/onSelectionChange→value/onChange。 - 受控的打开状态:新增
isOpen、defaultOpen、onOpenChange用于控制 Popover 的打开状态。 - 禁用选项:仍支持
disabledKeys以禁用指定菜单项。 - 必填:仍支持
isRequired标记字段为必填。 - 样式:
variant仅保留primary|secondary;color、size、radius已移除,请用 Tailwind CSS 扩展。 - classNames 已移除:在各子组件上使用
classNameprop。 - 内容类 prop 已移除:
startContent、endContent需通过自定义触发器实现。 - 清除按钮:
isClearable已移除,需手动实现。 - 自定义值:用
Select.Value的渲染 prop 替代renderValue。