组件工坊:Reflex 组件库 100 例
·
课程概览
布局
·
Box
·
Flex
·
Stack
·
Grid
·
Container
·
Section
·
Center
·
Spacer
·
Separator
·
Card
·
Inset
·
Aspect Ratio
·
Fragment
排版
·
Text
·
Heading
·
Link
·
Code
·
Markdown
·
Blockquote
·
Quote
·
Strong
·
Em
·
Kbd
表单
·
Button
·
Input
·
Text Area
·
Checkbox
·
Switch
·
Radio Group
·
Select
·
Slider
·
Form
·
Upload
数据展示
·
Badge
·
Avatar
·
Icon
·
Callout
·
List
·
Data List
·
Code Block
·
Progress
·
Spinner
·
Scroll Area
·
Moment
展开与切换
·
Tabs
·
Accordion
·
Segmented Control
浮层
·
Tooltip
·
Popover
·
Hover Card
·
Dialog
·
Alert Dialog
·
Drawer
·
Dropdown Menu
·
Context Menu
·
Toast
动态渲染
·
Cond
·
Match
·
Foreach
·
Auto Scroll
媒体
·
Image
·
Audio
·
Video
表格与数据网格
·
Table
·
Data Table
·
Data Editor
图表
·
Area Chart
·
Bar Chart
·
Line Chart
·
Composed Chart
·
Pie Chart
·
Radar Chart
·
Radial Bar Chart
·
Scatter Chart
·
Funnel Chart
·
Treemap
·
Sankey Chart
·
Error Bar
图表部件
·
Axis
·
Cartesian Grid
·
Chart Tooltip
·
Legend
·
Label
·
Reference
·
Brush
其他图表库
·
Plotly
·
Pyplot
HTML 元素
·
HTML
·
HTML Text
·
HTML Layout
·
HTML Forms
·
HTML Media
·
HTML Tables
·
SVG
其他工具
·
Theme
·
Skeleton
·
Clipboard
·
Html Embed
·
Script
·
Memo
rx.selectrx.select.rootrx.select.triggerrx.select.contentrx.select.grouprx.select.labelrx.select.itemrx.select.separator官方文档
工坊订单
请在商品列表上方加一个分类下拉框。下方只显示所选分类的商品。
rx.select 是一个下拉框,点击后展开列表,从中选一个值。它基于 Radix UI,可以用键盘选择,在桌面和移动端表现一致。只传一个列表,就会同时生成按钮和列表。
rx.select.root …)来做。rx.select(["apple", "grape", "pear"])placeholder 只在什么都没选时显示。position="popper" 时列表在按钮下方展开;"item-aligned"(默认)时,列表展开后已选项会正好叠在按钮的位置上。
试着改属性
placeholder
variant
size
color
radius
position
high_contrast
disabled
rx.select(["apple", "grape", "pear"], placeholder="Choose a fruit...", variant="surface", size="2", color_scheme="indigo", radius="medium", position="item-aligned", high_contrast=False, disabled=False)| 属性 | 取值 | 默认值 | 用途 |
|---|---|---|---|
第一个参数(items) | 字符串列表 / Var | 必填 | 选项列表,值与显示文字相同 |
value | 字符串 / Var | 无 | 受控的选中值(配合 on_change) |
default_value | 字符串 | 无 | 只在开始时选一次的值 |
placeholder | 字符串 | "" | 选择前按钮上显示的提示文字 |
label | 字符串 | 无 | 展开列表顶部的分组标题 |
variant | "classic" "surface" "soft" "ghost" | "surface" | 按钮外观 |
size | "1" ~ "3" | "2" | 大小 |
color_scheme, radius, high_contrast | 主题值 | 颜色、按钮圆角、列表对比度 | |
position | "item-aligned" "popper" | "item-aligned" | 列表的展开方式。在对话框里用 popper |
width | CSS 长度 | 无 | 按钮宽度 |
open / default_open | True / False | False | 控制列表是否展开 / 一开始就展开 |
name, required, disabled | 表单提交名、必须选择才能提交、禁用 | ||
on_change | 事件 | 选了别的选项时触发,传入值(字符串) | |
on_open_change | 事件 | 列表展开、收起时触发,传入 True/False |
这是最常见的用法。把 State 变量接到 value,把修改这个变量的处理器接到 on_change。选中项始终显示 State 的值,所以无论在应用哪里改了值,下拉框都会跟着变。
You selected:
appleclass SelectFruitState(rx.State):
selected_fruit: str = "apple"
@rx.event
def update_fruit(self, value: str):
self.selected_fruit = value
rx.hstack(
rx.select(
["apple", "grape", "pear"],
value=SelectFruitState.selected_fruit,
on_change=SelectFruitState.update_fruit,
),
rx.text("You selected:"),
rx.badge(SelectFruitState.selected_fruit),
align="center",
spacing="3",
)不需要 State、只想定初始选择时,用 default_value(非受控)。如果只在提交表单时才需要这个值,这样就够了。想以空状态开始、引导用户选择,就去掉 default_value,改给 placeholder。disabled=True 时会变灰,也无法展开。
rx.flex(
rx.select(["apple", "grape", "pear"], default_value="grape"),
rx.select(["apple", "grape", "pear"], placeholder="Choose a fruit..."),
rx.select(["apple", "grape", "pear"], default_value="apple", disabled=True),
spacing="3",
wrap="wrap",
)选项通常来自数据库或 API。传入 State 里的列表 Var 而不是普通列表,列表变了,下拉选项也会变。内部用 rx.foreach 生成选项。
3 options
import random
class SelectDynamicState(rx.State):
options: list[str] = ["apple", "grape", "pear"]
selected: str = "apple"
@rx.event
def set_selected(self, value: str):
self.selected = value
@rx.event
def randomize(self):
self.selected = random.choice(self.options)
@rx.event
def add_option(self):
new_options = ["banana", "orange", "mango", "kiwi", "cherry"]
available = [o for o in new_options if o not in self.options]
if available:
self.options = self.options + [available[0]]
@rx.event
def reset_options(self):
self.options = ["apple", "grape", "pear"]
self.selected = "apple"
rx.vstack(
rx.select(
SelectDynamicState.options,
value=SelectDynamicState.selected,
on_change=SelectDynamicState.set_selected,
),
rx.hstack(
rx.button("Pick Random", on_click=SelectDynamicState.randomize, size="1"),
rx.button("Add Option", on_click=SelectDynamicState.add_option, size="1", variant="soft"),
rx.button("Reset", on_click=SelectDynamicState.reset_options, size="1", variant="soft", color_scheme="gray"),
),
rx.text(SelectDynamicState.options.length(), " options", size="1", color_scheme="gray"),
)name 是提交数据的键,required=True 时选择之前无法提交。没有选择的 select 会以空值("")进入数据。
Results:
{}class SelectFormState(rx.State):
form_data: dict = {}
@rx.event
def handle_submit(self, form_data: dict):
self.form_data = form_data
rx.card(
rx.vstack(
rx.form.root(
rx.vstack(
rx.text("Favorite fruit", size="2"),
rx.select(["apple", "grape", "pear"], name="fruit", placeholder="Pick one", required=True),
rx.text("Quantity", size="2"),
rx.select(["1", "2", "3", "4", "5"], name="quantity", default_value="1"),
rx.button("Submit Order", type="submit"),
spacing="2",
align="stretch",
),
on_submit=SelectFormState.handle_submit,
reset_on_submit=True,
),
rx.divider(),
rx.hstack(rx.text("Results:", weight="bold"), rx.badge(SelectFormState.form_data.to_string())),
spacing="3",
),
width="320px",
)高层 rx.select 的值与显示文字相同。如果要显示用户名、实际使用 ID,可以像官方文档那样,用计算变量从名字查出 ID。
Selected user ID: user_001
class SelectUserState(rx.State):
users: dict[str, str] = {
"user_001": "Alice Johnson",
"user_002": "Bob Smith",
"user_003": "Carol Davis",
}
selected_name: str = "Alice Johnson"
@rx.var
def user_names(self) -> list[str]:
return list(self.users.values())
@rx.var
def selected_id(self) -> str:
for uid, name in self.users.items():
if name == self.selected_name:
return uid
return ""
@rx.event
def set_user(self, name: str):
self.selected_name = name
rx.vstack(
rx.select(
SelectUserState.user_names,
value=SelectUserState.selected_name,
on_change=SelectUserState.set_user,
),
rx.text("Selected user ID: ", rx.code(SelectUserState.selected_id), size="2"),
)想直接在下拉框里分开,就用底层 API,写成 rx.select.item("显示文字", value="值")。on_change 传入的是 value。
value = kr
class SelectCountryState(rx.State):
code: str = "kr"
@rx.event
def set_code(self, value: str):
self.code = value
rx.hstack(
rx.select.root(
rx.select.trigger(),
rx.select.content(
rx.select.item("Korea", value="kr"),
rx.select.item("Japan", value="jp"),
rx.select.item("Germany", value="de"),
),
value=SelectCountryState.code,
on_change=SelectCountryState.set_code,
),
rx.text("value = ", rx.code(SelectCountryState.code), size="2"),
align="center",
)在 Dialog 这类浮在页面上方的容器里,要给 position="popper"。这样列表才不会被对话框裁掉,而是在按钮正下方展开。
rx.dialog.root(
rx.dialog.trigger(rx.button("Open Dialog")),
rx.dialog.content(
rx.dialog.title("Pick a fruit"),
rx.vstack(
rx.select(["apple", "grape", "pear"], position="popper", default_value="apple"),
rx.dialog.close(rx.button("Close", variant="soft")),
spacing="3",
),
max_width="320px",
),
)on_open_change 在列表展开时传入 True,收起时传入 False。可以用来在展开时预先加载数据,或者统计次数。
Open count: 0
Closedclass SelectOpenState(rx.State):
is_open: bool = False
open_count: int = 0
@rx.event
def on_toggle(self, is_open: bool):
self.is_open = is_open
if is_open:
self.open_count += 1
rx.vstack(
rx.select(["apple", "grape", "pear"], default_value="apple", on_open_change=SelectOpenState.on_toggle),
rx.hstack(
rx.text("Open count: ", SelectOpenState.open_count, size="2"),
rx.cond(
SelectOpenState.is_open,
rx.badge("Open", color_scheme="green"),
rx.badge("Closed", color_scheme="gray"),
),
align="center",
),
)高层 rx.select 只是替你把下面这些部件组装好。自己组装的话,就能加入分组标题和分隔线,或者只禁用某一个选项。
| 部件 | 职责 | 主要属性 |
|---|---|---|
rx.select.root | 整体与选中值 | value default_value on_change open default_open on_open_change name required disabled size |
rx.select.trigger | 点击的按钮 | placeholder variant color_scheme radius |
rx.select.content | 展开的列表 | position side side_offset align align_offset variant("solid" "soft") color_scheme high_contrast |
rx.select.group | 选项分组 | |
rx.select.label | 分组标题 | |
rx.select.item | 单个选项 | value(必填) disabled |
rx.select.separator | 分隔线 |
rx.select.content 还有 on_escape_key_down、on_pointer_down_outside、on_close_auto_focus 事件。下面是一个带两个分组、一条分隔线和一个禁用选项(Grape)的列表。
rx.select.root(
rx.select.trigger(placeholder="Pick a food", variant="soft", radius="full"),
rx.select.content(
rx.select.group(
rx.select.label("Fruits"),
rx.select.item("Orange", value="orange"),
rx.select.item("Apple", value="apple"),
rx.select.item("Grape", value="grape", disabled=True),
),
rx.select.separator(),
rx.select.group(
rx.select.label("Vegetables"),
rx.select.item("Carrot", value="carrot"),
rx.select.item("Potato", value="potato"),
),
position="popper",
side="bottom",
side_offset=6,
variant="soft",
color_scheme="green",
),
size="2",
)在底层 root 上,同样可以用 value + on_change 让 State 掌管选中项,用 open + on_open_change 掌管展开状态。可以用按钮切换选中项或展开列表。注意:如果 on_open_change 不去更新 open,点按钮时列表既不会展开也不会收起。
在表单中,root 的 name 是键,选中 item 的 value 是值。
class SelectControlState(rx.State):
value: str = "apple"
open: bool = False
@rx.event
def set_value(self, value: str):
self.value = value
@rx.event
def set_open(self, value: bool):
self.open = value
@rx.event
def next_fruit(self):
fruits = ["apple", "grape", "pear"]
self.value = fruits[(fruits.index(self.value) + 1) % 3]
rx.vstack(
rx.select.root(
rx.select.trigger(),
rx.select.content(
rx.select.item("apple", value="apple"),
rx.select.item("grape", value="grape"),
rx.select.item("pear", value="pear"),
),
value=SelectControlState.value,
on_change=SelectControlState.set_value,
open=SelectControlState.open,
on_open_change=SelectControlState.set_open,
),
rx.hstack(
rx.button("Next value", on_click=SelectControlState.next_fruit, size="1", variant="soft"),
rx.button("Open menu", on_click=SelectControlState.set_open(True), size="1", variant="soft"),
),
)后一个 select 的选项随前一个 select 的值变化(地区 → 国家)。换地区时,处理器把国家也设成该地区的第一个国家,避免后一个 select 指向不存在的值。
class SelectCascadeState(rx.State):
regions: dict[str, list[str]] = {
"North America": ["USA", "Canada", "Mexico"],
"Europe": ["UK", "France", "Germany"],
"Asia": ["Japan", "Korea", "Singapore"],
}
region: str = "North America"
country: str = "USA"
@rx.event
def set_region(self, value: str):
self.region = value
self.country = self.regions[value][0]
@rx.event
def set_country(self, value: str):
self.country = value
@rx.var
def region_names(self) -> list[str]:
return list(self.regions.keys())
@rx.var
def countries(self) -> list[str]:
return self.regions.get(self.region, [])
rx.hstack(
rx.select(SelectCascadeState.region_names, value=SelectCascadeState.region, on_change=SelectCascadeState.set_region),
rx.select(SelectCascadeState.countries, value=SelectCascadeState.country, on_change=SelectCascadeState.set_country),
spacing="3",
wrap="wrap",
)这是用 select 决定下方显示哪些数据的筛选模式。计算变量 filtered_items 只返回所选分类的商品,rx.foreach 把它们排成卡片。label 显示在展开列表顶部,作为分组标题。
class SelectFilterState(rx.State):
all_items: list[dict[str, str]] = [
{"name": "MacBook Pro", "category": "laptops"},
{"name": "Dell XPS", "category": "laptops"},
{"name": "iPhone 15", "category": "phones"},
{"name": "Pixel 8", "category": "phones"},
{"name": "iPad Air", "category": "tablets"},
{"name": "Galaxy Tab", "category": "tablets"},
]
category: str = "laptops"
@rx.event
def set_category(self, value: str):
self.category = value
@rx.var
def filtered_items(self) -> list[dict[str, str]]:
return [i for i in self.all_items if i["category"] == self.category]
rx.vstack(
rx.select(
["laptops", "phones", "tablets"],
value=SelectFilterState.category,
on_change=SelectFilterState.set_category,
label="Category",
),
rx.foreach(SelectFilterState.filtered_items, lambda item: rx.card(item["name"], width="100%")),
spacing="3",
width="280px",
)工坊笔记
用 rx.select([选项...]) 创建,用 value + on_change 与 State 绑定。只定初始值用 default_value。
选择前的提示用 placeholder,在对话框中用 position="popper"。
把选项作为 State 列表传入,下拉列表就会跟随数据变化。
显示文字与值不同,或需要分组·分隔线·禁用选项时,用 root · trigger · content · group · label · item · separator。
on_open_change 传入展开(True)/收起(False)。用 open 直接控制时,处理器必须更新 open。
订阅新课时和新课程通知
本课程新增课时或推出后续课程时,我们会发邮件通知你。
问答
关于本课有任何疑问都可以在这里留言。所有语言版本页面的提问会汇集在一起。