Reflex 数据看板实战教程:用纯 Python 构建可交互的数据表格、表单与图表应用
本教程基于 Reflex 官方 Getting Started 系列中的《Data Dashboard》文档(仓库路径 docs/getting_started/dashboard_tutorial.md)整理编写,并结合 Reflex 仓库源码(表格、Recharts 图表组件实现)进行纵深补充。你将亲手构建一个约 20 分钟即可完成的交互式数据看板:一张用户数据表格、一个用于新增用户的表单(置于对话框内)、以及一个随数据变化实时更新的柱状图。学完本篇,你将掌握 Reflex 中最核心的几项能力:组件与 props 的用法、State 动态数据、事件处理器(Event Handler)、表单提交、数据可视化与主题定制——这些技巧适用于任何 Reflex 应用。
你将构建什么
一个可交互的数据看板,包含三部分核心功能:
- 用户表格:展示用户的姓名(Name)、邮箱(Email)、性别(Gender);
- 新增用户表单:点击 "Add User" 按钮弹出对话框,填写信息后提交,新用户立即出现在表格中;
- 柱状图:按性别统计用户数量,表格数据变化时图表同步更新。
本教程不假设你有任何 Reflex 基础,但官方建议先阅读 Basics 快速入门指南(其中讲解了组件、State、事件等基础概念,以及编译期与运行期的区别)。下文将按照 Setup → Overview(组件与 props)→ 用 State 实现动态数据 → 用表单添加数据 → 绘制图表 → 自定义与完整应用 的路径逐步推进。
环境准备与项目初始化
安装 Reflex
如果尚未安装 Reflex,请先参照 安装指南 完成安装。
初始化项目
- 创建一个名为
dashboard_tutorial的文件夹并进入; - 运行
uv init初始化项目,然后运行uv add reflex添加依赖; - 运行
uv run reflex init初始化应用,选择模板0(空白模板); - 运行
uv run reflex run启动应用,确认一切正常后即可开始编码。
说明:本仓库为 Reflex 的开源单体仓库(monorepo),核心源码位于 reflex/ 目录,各功能组件拆分为独立包(如
packages/reflex-components-radix、packages/reflex-components-recharts)。教程中所有rx.*组件的真实实现均可在这几个包中溯源。若你是从零开始的新项目,按上述步骤使用uv创建独立应用即可。
最小应用骨架:组件函数 + App 注册
reflex init 会生成以下结构:
rxconfig.py:应用配置文件(详见 configuration 文档);assets/:存放静态文件的目录;dashboard_tutorial/dashboard_tutorial.py:包含你应用的模块。
打开该模块并清空内容,我们从零开始构建。
一个最小的 Reflex 页面就是一个组件函数加上一个注册它的 App:
import reflex as rx
def index() -> rx.Component:
return rx.text("Hello World!")
app = rx.App()
app.add_page(index)
组件函数返回一个组件树,rx.text 是最基础的文本组件。App 注册是每个 Reflex 应用必需的:app = rx.App() 创建应用实例,app.add_page(index) 将 index 组件注册为页面。
提示:为简洁起见,下文各小节代码中省略了
app = rx.App()与app.add_page(index)两行,我们会在「自定义」小节回到它们。
创建静态表格:认识组件树与 props
表格是看板的核心展示区。Reflex 的 rx.table 组件(基于 Radix UI Themes 封装,源码见 table.py)采用语义化的层级结构:
rx.table.root:表格根节点,最外层容器;rx.table.header:表头区,内部放rx.table.row(表头行)→rx.table.column_header_cell(列标题单元格);rx.table.body:表体区,内部放rx.table.row(数据行)→rx.table.cell(数据单元格)。
从源码可以看到完整的组件族(table.py):TableRoot、TableHeader、TableBody、TableRow、TableCell、TableColumnHeaderCell、TableRowHeaderCell,它们通过 Table 命名空间(namespace)以 rx.table.root、rx.table.header 等形式暴露。
rx.table.root 支持 variant 与 size 两个外观 props(源码中定义于 table.py):
| Prop | 可选值 | 作用 |
|---|---|---|
variant |
"surface"、"ghost" |
表格的视觉样式:surface 带背景与边框,ghost 为无边框幽灵样式 |
size |
"1"、"2"、"3" |
表格的尺寸(影响行高与内边距) |
此外 TableRow 支持 align(start/center/end/baseline),TableCell 与 TableColumnHeaderCell 支持 justify、min_width、max_width 等。
先用硬编码的数据写一个静态表格:
def index() -> rx.Component:
return rx.table.root(
rx.table.header(
rx.table.row(
rx.table.column_header_cell("Name"),
rx.table.column_header_cell("Email"),
rx.table.column_header_cell("Gender"),
),
),
rx.table.body(
rx.table.row(
rx.table.cell("Danilo Sousa"),
rx.table.cell("danilo@example.com"),
rx.table.cell("Male"),
),
rx.table.row(
rx.table.cell("Zahra Ambessa"),
rx.table.cell("zahra@example.com"),
rx.table.cell("Female"),
),
),
variant="surface",
size="3",
)
这里可以看出 Reflex 组件树的组织方式:子组件作为父组件的参数传入,props 以关键字参数传入。表头用 column_header_cell(语义化表头),表体用 cell(数据单元格)。
用 State 实现动态数据
上面的表格是静态的——行是写死的。要让数据动起来,需要把数据搬到 State 上。在 Reflex 中:
- State 是一个继承
rx.State的 Python 类,它的类字段称为 state vars(状态变量),保存应用的数据(详见 State 概述); - State 的方法称为 event handlers(事件处理器),负责修改这些数据(详见 事件概述)。
用 dataclass 建模用户
我们用一个 User dataclass 来建模每一行数据,这样就能按字段名访问(user.name)而不是按索引:
import dataclasses
@dataclasses.dataclass
class User:
name: str
email: str
gender: str
class State(rx.State):
users: list[User] = [
User(name="Danilo Sousa", email="danilo@example.com", gender="Male"),
User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"),
]
users 是一个 list[User] 类型的 state var,初始包含两条数据。
用 rx.foreach 渲染列表
要遍历列表类型的 state var,必须使用 rx.foreach(详见 rendering_iterables 文档)。它接收一个可迭代对象和一个渲染函数,渲染函数接收列表中的每个元素并返回对应的组件。这里 show_user 接收一个 User,返回一行 table.row:
def show_user(user: User) -> rx.Component:
return rx.table.row(
rx.table.cell(user.name),
rx.table.cell(user.email),
rx.table.cell(user.gender),
)
def index() -> rx.Component:
return rx.table.root(
rx.table.header(
rx.table.row(
rx.table.column_header_cell("Name"),
rx.table.column_header_cell("Email"),
rx.table.column_header_cell("Gender"),
),
),
rx.table.body(
rx.foreach(State.users, show_user),
),
variant="surface",
size="3",
)
表格看起来和之前一模一样,但行数据已经来自 state——接下来加一个表单,往 State.users 里追加数据,新行就会自动出现。
为什么不能用普通的 for 循环?
这是一个关键问题。普通的 for 循环在编译期(compile time)执行,此时 state vars 还是初始值;而 state vars 是在**运行期(runtime)**变化的——如果你用 for 循环写死渲染行数,当用户新增一条数据时,前端不会知道要重新渲染,行数不会更新。rx.foreach 则告诉编译器:当这个 state var 变化时重新渲染对应区域。更多细节见 Basics 指南中的"编译期 vs 运行期"。
用表单添加数据:事件触发器与事件处理器
认识 rx.form 与表单字段组件
我们用 rx.form 构建表单(完整表单组件文档见 form),它内部包含多个字段组件:
rx.input:文本输入框。关键 props:placeholder:输入框为空时显示的提示文字;name:字段名,表单提交时会以这个 name 作为字典的 key 传递数据;required:布尔值,是否必填(浏览器原生校验)。
rx.select:下拉选择框,接收一个选项列表,其余 props 与rx.input一致。
最基本的表单如下(未加样式,非常紧凑):
rx.form(
rx.input(placeholder="User Name", name="name", required=True),
rx.input(
placeholder="user@reflex.dev",
name="email",
),
rx.select(
["Male", "Female"],
placeholder="Male",
name="gender",
),
)
用 vstack 做纵向布局
紧凑的表单可读性差。用 rx.vstack 组件把字段纵向堆叠起来(布局相关文档见 styling/layout):
rx.form(
rx.vstack(
rx.input(placeholder="User Name", name="name", required=True),
rx.input(
placeholder="user@reflex.dev",
name="email",
),
rx.select(
["Male", "Female"],
placeholder="Male",
name="gender",
),
),
)
添加提交按钮与事件处理器
现在字段齐全了,但还无法提交。需要两件事:
- 提交按钮:在
vstack里加一个rx.button,文本显示在按钮上,typeprop 设为"submit",点击时触发表单提交; - 事件处理器:所有 state 的变更都通过 State 类中的函数完成,这些函数被称为 event handlers。组件有特殊的 props 称为事件触发器(event triggers),例如
on_submit,它们把组件连接到事件处理器上,从而更新 state。不同的事件触发器对事件处理器的签名要求不同——有的不传参,有的传特定参数。
rx.form 的 on_submit 触发器要求它所连接的处理器接收一个 dict(表单数据)。我们把 on_submit 接到 State 类中定义的 add_user 事件处理器上,它接收表单数据字典并追加到 users state var:
class State(rx.State):
...
def add_user(self, form_data: dict):
self.users.append(User(**form_data))
def form():
return rx.form(
rx.vstack(
rx.input(placeholder="User Name", name="name", required=True),
rx.input(
placeholder="user@reflex.dev",
name="email",
),
rx.select(
["Male", "Female"],
placeholder="Male",
name="gender",
),
rx.button("Submit", type="submit"),
),
on_submit=State.add_user,
reset_on_submit=True,
)
注意这里 User(**form_data) 用字典解包的方式构造 User——字典的 key 正是表单字段的 name(name、email、gender),恰好与 User dataclass 的字段一一对应。reset_on_submit=True 表示提交成功后自动清空表单。
最后把 form() 组件加进 index() 函数里,表单就会渲染在页面上。此时完整流程已经跑通:填写表单 → 点击 Submit → add_user 被调用 → users 更新 → 表格自动多出一行。若事件处理器返回一个 rx.toast.info(...)(如文档中的增强版示例),提交后页面角落还会弹出 toast 提示小窗口:
def add_user(self, form_data: dict):
self.users.append(User(**form_data))
return rx.toast.info(
f"User has been added: {form_data}.",
position="bottom-right",
)
完整代码如下(此时 toast 部分按你喜好取舍):
class State(rx.State):
users: list[User] = [
User(name="Danilo Sousa", email="danilo@example.com", gender="Male"),
User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"),
]
def add_user(self, form_data: dict):
self.users.append(User(**form_data))
def show_user(user: User):
"""Show a person in a table row."""
return rx.table.row(
rx.table.cell(user.name),
rx.table.cell(user.email),
rx.table.cell(user.gender),
)
def form():
return rx.form(
rx.vstack(
rx.input(placeholder="User Name", name="name", required=True),
rx.input(
placeholder="user@reflex.dev",
name="email",
),
rx.select(
["Male", "Female"],
placeholder="Male",
name="gender",
),
rx.button("Submit", type="submit"),
),
on_submit=State.add_user,
reset_on_submit=True,
)
def index() -> rx.Component:
return rx.vstack(
form(),
rx.table.root(
rx.table.header(
rx.table.row(
rx.table.column_header_cell("Name"),
rx.table.column_header_cell("Email"),
rx.table.column_header_cell("Gender"),
),
),
rx.table.body(
rx.foreach(State.users, show_user),
),
variant="surface",
size="3",
),
)
把表单放进对话框(Modal)
Reflex 追求最直观的用户交互。把表单放进一个覆盖层(overlay)中,会通过调暗背景创造聚焦的交互体验,而且当你有编辑、删除等多个操作入口时,页面布局也会更干净。
我们使用 rx.dialog 组件(即模态框)来承载表单,其层级结构如下:
rx.dialog.root:对话框根节点,包含全部组成部分;rx.dialog.trigger:包裹打开对话框的控件——这里是写着 "Add User" 的rx.button;rx.dialog.content:对话框内部的所有内容,包括title(标题)、description(描述)和我们的表单;rx.dialog.close:包裹关闭对话框的控件——这里是 "Cancel" 取消按钮;- 提交按钮 "Submit" 留在表单内部,表单的
on_submit处理器只在必填字段通过校验后才关闭对话框。
触发按钮:
rx.dialog.trigger(
rx.button(
rx.icon("plus", size=26),
rx.text("Add User", size="4"),
),
)
取消按钮(放在对话框内容里):
rx.dialog.close(
rx.button(
"Cancel",
variant="soft",
color_scheme="gray",
),
)
受控对话框:用 State 控制开关
要在合法提交后自动关闭对话框,需要新增对话框专用的 state:一个布尔型 state var add_user_dialog_open 记录对话框开关状态,set_add_user_dialog_open 事件处理器修改它;add_user 在成功追加用户后把它设为 False。之所以这样设计,是因为浏览器在必填字段为空时不会调用 add_user,此时对话框保持打开,由浏览器原生校验提示用户。
class State(rx.State):
users: list[User] = [
User(name="Danilo Sousa", email="danilo@example.com", gender="Male"),
User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"),
]
add_user_dialog_open: bool = False
def set_add_user_dialog_open(self, open_: bool):
"""Set whether the add-user dialog is open."""
self.add_user_dialog_open = open_
def add_user(self, form_data: dict):
self.users.append(User(**form_data))
self.add_user_dialog_open = False
return rx.toast.info(
f"User has been added: {form_data}.",
position="bottom-right",
)
rx.dialog.root 通过 open=State.add_user_dialog_open 与 on_open_change=State.set_add_user_dialog_open 两个 props 与 state 绑定,实现受控对话框(open 状态完全由 state 决定)。
对话框内完整表单代码
下面把上述内容组装成 add_customer_button 组件(从 form 改名而来,语义更明确)。表单内部用 rx.flex 做纵向布局(direction="column"),按钮行用 justify="end" 靠右对齐;max_width="450px" 限制对话框宽度:
def add_customer_button() -> rx.Component:
return rx.dialog.root(
rx.dialog.trigger(
rx.button(
rx.icon("plus", size=26),
rx.text("Add User", size="4"),
),
),
rx.dialog.content(
rx.dialog.title(
"Add New User",
),
rx.dialog.description(
"Fill the form with the user's info",
),
rx.form(
rx.flex(
rx.input(placeholder="User Name", name="name", required=True),
rx.input(
placeholder="user@reflex.dev",
name="email",
),
rx.select(
["Male", "Female"],
placeholder="male",
name="gender",
),
rx.flex(
rx.dialog.close(
rx.button(
"Cancel",
variant="soft",
color_scheme="gray",
),
),
rx.button("Submit", type="submit"),
spacing="3",
justify="end",
),
direction="column",
spacing="4",
),
on_submit=State.add_user,
reset_on_submit=False,
),
max_width="450px",
),
open=State.add_user_dialog_open,
on_open_change=State.set_add_user_dialog_open,
)
此时应用已经支持:点击 "Add User" 按钮打开对话框 → 填写表单 → 提交后新用户进入表格、对话框关闭。注意这里 reset_on_submit=False:因为表单在对话框里,关闭对话框时表单状态由组件自行处理,不必提交后立即清空。
对应的 index():
def index() -> rx.Component:
return rx.vstack(
add_customer_button(),
rx.table.root(
rx.table.header(
rx.table.row(
rx.table.column_header_cell("Name"),
rx.table.column_header_cell("Email"),
rx.table.column_header_cell("Gender"),
),
),
rx.table.body(
rx.foreach(State.users, show_user),
),
variant="surface",
size="3",
),
)
绘制图表:Recharts 柱状图
接下来用 Reflex 内置的 recharts 库绘制柱状图,统计各性别的用户数量。
数据转换:把状态数据变成图表数据
Reflex 的图表组件期望接收一个字典列表,每个字典代表图上的一个数据点,包含 x 值和 y 值。因此我们在 State 中新增:
- 一个事件处理器
transform_data:把用户数据转换成图表需要的格式; - 一个 state var
users_for_graph:存储转换后的数据,供图表渲染。
from collections import Counter
class State(rx.State):
users: list[User] = []
users_for_graph: list[dict] = []
def add_user(self, form_data: dict):
self.users.append(User(**form_data))
self.transform_data()
def transform_data(self):
"""Transform user gender group data into a format suitable for visualization in graphs."""
# Count users of each gender group
gender_counts = Counter(user.gender for user in self.users)
# Transform into list of dict so it can be used in the graph
self.users_for_graph = [
{"name": gender_group, "value": count}
for gender_group, count in gender_counts.items()
]
transform_data 使用标准库 collections.Counter 统计每种性别的用户数量,再把结果转成 [{"name": ..., "value": ...}, ...] 形式的字典列表写入 users_for_graph。同时,add_user 每次追加用户后都会调用 transform_data,保证图表数据始终与表格数据同步。
渲染柱状图
用 rx.recharts.bar_chart 渲染图表(Recharts 组件包源码见 packages/reflex-components-recharts):
- 通过
data=State.users_for_graph传入图表数据; rx.recharts.bar:柱条组件。data_keyprop 指定数据字典中代表 y 值的 key(这里是"value");stroke与fill设置柱条描边与填充颜色;rx.recharts.x_axis:x 轴。data_key指定数据字典中代表 x 值的 key(这里是"name");rx.recharts.y_axis:y 轴;width与height设置图表尺寸。
def graph():
return rx.recharts.bar_chart(
rx.recharts.bar(
data_key="value",
stroke=rx.color("accent", 9),
fill=rx.color("accent", 8),
),
rx.recharts.x_axis(data_key="name"),
rx.recharts.y_axis(),
data=State.users_for_graph,
width="100%",
height=250,
)
从源码看,BarChart 类(charts.py)还支持 bar_category_gap(分类间距,默认 "10%")、bar_gap(同分类内柱间距,默认 4)、bar_size、max_bar_size、stack_offset 等 props;Bar 类(cartesian.py)支持 bar_size(柱宽)、radius(圆角)、stack_id(堆叠分组)、min_point_size 等;fill 默认值为 rx.color("accent", 9)。这些 props 全部可以直接在组件上使用。
把 graph() 加入 index(),图表就会渲染在页面上,且每次新增用户时自动更新:
def index() -> rx.Component:
return rx.vstack(
add_customer_button(),
rx.table.root(
rx.table.header(
rx.table.row(
rx.table.column_header_cell("Name"),
rx.table.column_header_cell("Email"),
rx.table.column_header_cell("Gender"),
),
),
rx.table.body(
rx.foreach(State.users, show_user),
),
variant="surface",
size="3",
),
graph(),
)
一个潜在问题:空数据时的图表
如果你本地运行且没有任何种子用户,图表会是空的——因为 transform_data 只在 add_user 被调用时执行。下一节通过 on_load 在页面加载时调用它来解决这个问题。
自定义应用
重新审视 app.add_page
教程开头提过 app.add_page 是每个 Reflex 应用必需的注册函数。目前它是 app.add_page(index)。它可以接收多个关键 props:
route:页面路由。例如route="/custom-route"会让页面渲染在http://localhost:3000/custom-route;title:显示在浏览器标签页的标题;description:显示在搜索结果中的描述;on_load:页面加载时触发的事件处理器。用它解决上面的问题——页面加载即调用transform_data初始化图表。
app.add_page(
index,
title="Customer Data App",
description="A simple app to manage customer data.",
on_load=State.transform_data,
)
重新审视 rx.App()
app = rx.App() 也可以传入 props 定制整个应用。最常用的是 theme——它接收一个 rx.theme 组件,该组件有几个关键 props:
radius:应用的全局圆角值,被所有带radiusprop 的组件继承;单个组件可以手动设置radius覆盖它;accent_color:应用的主题色。完整的主题选项见 theme 组件文档,更多应用级 props 见 theming 文档。
app = rx.App(
theme=rx.theme(radius="full", accent_color="grass"),
)
主题作用于整个应用级别,需要本地运行才能看到实际效果。
完整应用(带样式)
最后做一轮样式优化:
- 在
show_user中给表格行加 hover 高亮:style={"_hover": {"bg": rx.color("gray", 3)}},并设置align="center"让单元格内容居中; - 给
index()添加width="100%"与align="center",让页面元素占满宽度并居中; - 图表进一步美化:柱条加圆角
radius=6、固定柱宽bar_size=48,x/y 轴去掉刻度线,添加虚线网格cartesian_grid,并通过margin控制图表边距。
下面是完整、可直接运行的应用代码(来自文档末尾的完整版本):
import reflex as rx
from collections import Counter
@dataclasses.dataclass
class User:
"""The user model."""
name: str
email: str
gender: str
class State(rx.State):
users: list[User] = [
User(name="Danilo Sousa", email="danilo@example.com", gender="Male"),
User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"),
]
users_for_graph: list[dict] = []
add_user_dialog_open: bool = False
def set_add_user_dialog_open(self, open_: bool):
"""Set whether the add-user dialog is open."""
self.add_user_dialog_open = open_
def add_user(self, form_data: dict):
self.users.append(User(**form_data))
self.transform_data()
self.add_user_dialog_open = False
def transform_data(self):
"""Transform user gender group data into a format suitable for visualization in graphs."""
# Count users of each gender group
gender_counts = Counter(user.gender for user in self.users)
# Transform into list of dict so it can be used in the graph
self.users_for_graph = [
{"name": gender_group, "value": count}
for gender_group, count in gender_counts.items()
]
def show_user(user: User):
"""Show a user in a table row."""
return rx.table.row(
rx.table.cell(user.name),
rx.table.cell(user.email),
rx.table.cell(user.gender),
style={"_hover": {"bg": rx.color("gray", 3)}},
align="center",
)
def add_customer_button() -> rx.Component:
return rx.dialog.root(
rx.dialog.trigger(
rx.button(
rx.icon("plus", size=26),
rx.text("Add User", size="4"),
),
),
rx.dialog.content(
rx.dialog.title(
"Add New User",
),
rx.dialog.description(
"Fill the form with the user's info",
),
rx.form(
rx.flex(
rx.input(placeholder="User Name", name="name", required=True),
rx.input(
placeholder="user@reflex.dev",
name="email",
),
rx.select(
["Male", "Female"],
placeholder="male",
name="gender",
),
rx.flex(
rx.dialog.close(
rx.button(
"Cancel",
variant="soft",
color_scheme="gray",
),
),
rx.button("Submit", type="submit"),
spacing="3",
justify="end",
),
direction="column",
spacing="4",
),
on_submit=State.add_user,
reset_on_submit=False,
),
max_width="450px",
),
open=State.add_user_dialog_open,
on_open_change=State.set_add_user_dialog_open,
)
def graph():
return rx.recharts.bar_chart(
rx.recharts.bar(
data_key="value",
fill=rx.color("accent", 9),
radius=6,
bar_size=48,
),
rx.recharts.x_axis(
data_key="name",
tick_line=False,
axis_line=False,
padding={"left": 24, "right": 24},
),
rx.recharts.y_axis(
tick_line=False,
axis_line=False,
allow_decimals=False,
),
rx.recharts.cartesian_grid(
stroke_dasharray="3 3",
vertical=False,
stroke=rx.color("slate", 4),
),
data=State.users_for_graph,
width="100%",
height=200,
margin={"top": 8, "right": 8, "bottom": 0, "left": 0},
)
def index() -> rx.Component:
return rx.box(
rx.vstack(
rx.hstack(
rx.vstack(
rx.text(
"Users",
size="4",
weight="bold",
color=rx.color("slate", 12),
text_align="left",
width="100%",
),
rx.text(
"Add customers and watch the chart update.",
size="2",
color=rx.color("slate", 10),
text_align="left",
width="100%",
),
spacing="1",
align="start",
),
rx.spacer(),
add_customer_button(),
align="center",
width="100%",
),
rx.table.root(
rx.table.header(
rx.table.row(
rx.table.column_header_cell("Name"),
rx.table.column_header_cell("Email"),
rx.table.column_header_cell("Gender"),
),
),
rx.table.body(
rx.foreach(State.users, show_user),
),
variant="surface",
size="2",
width="100%",
),
graph(),
align="stretch",
width="100%",
spacing="4",
padding="1.75em 2em",
),
border=f"1px solid {rx.color('slate', 5)}",
border_radius="12px",
margin_y="1em",
background=rx.color("slate", 1),
)
app = rx.App(
theme=rx.theme(radius="full", accent_color="grass"),
)
app.add_page(
index,
title="Customer Data App",
description="A simple app to manage customer data.",
on_load=State.transform_data,
)
这段代码中值得留意的布局与样式要点:
rx.color(scale, step):Reflex 的色彩系统。第一个参数是色阶名称(如"accent"、"slate"、"gray"),第二个参数是 1–12 的梯度值。它让组件颜色跟随应用主题(例如accent_color="grass"时accent自动变绿),详见 theming 文档;rx.hstack/rx.vstack:水平/垂直堆叠布局;rx.spacer()撑开剩余空间把标题与按钮分居两侧;style={"_hover": ...}:伪类样式,实现鼠标悬停行高亮;on_load=State.transform_data:页面加载即初始化图表,解决空数据问题。
核心概念回顾
你构建了一个完整的数据看板应用:
- 一个展示用户数据的表格;
- 一个位于对话框内的表单,用于新增用户;
- 一个实时可视化性别分布的柱状图。
在此过程中你掌握了 Reflex 的三项核心能力:
- State:如何存储随时间变化的数据。State 类中的字段是 state var,页面通过
rx.foreach等机制响应它的变化; - Events:如何响应用户操作并更新 UI。
on_submit、on_open_change、on_load等事件触发器把组件与事件处理器相连,事件处理器修改 state 后 UI 自动重渲染; - Styling:如何调整主题(
rx.theme的radius、accent_color)、布局(hstack/vstack/flex)与交互状态(hover 伪类)。
延伸阅读
- Basics 快速入门:组件、State、事件的基础概念,以及编译期与运行期的区别;
- State 概述:state vars 的完整机制;
- 事件概述:事件处理器与事件触发器的详细说明;
- 表单组件文档:
rx.form及全部表单字段组件; - rendering_iterables:
rx.foreach的进阶用法; - theme 组件文档 与 theming:主题定制;
- 组件源码:
rx.table见 table.py,Recharts 图表见 charts.py 与 cartesian.py。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K637- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python270
cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端TypeScript2 K146
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python46066
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go20143
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java34051