首页
/ Reflex 数据看板实战教程:用纯 Python 构建可交互的数据表格、表单与图表应用

Reflex 数据看板实战教程:用纯 Python 构建可交互的数据表格、表单与图表应用

2026-09-11 14:24:13作者:范垣楠Rhoda

本教程基于 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,请先参照 安装指南 完成安装。

初始化项目

  1. 创建一个名为 dashboard_tutorial 的文件夹并进入;
  2. 运行 uv init 初始化项目,然后运行 uv add reflex 添加依赖;
  3. 运行 uv run reflex init 初始化应用,选择模板 0(空白模板);
  4. 运行 uv run reflex run 启动应用,确认一切正常后即可开始编码。

说明:本仓库为 Reflex 的开源单体仓库(monorepo),核心源码位于 reflex/ 目录,各功能组件拆分为独立包(如 packages/reflex-components-radixpackages/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):TableRootTableHeaderTableBodyTableRowTableCellTableColumnHeaderCellTableRowHeaderCell,它们通过 Table 命名空间(namespace)以 rx.table.rootrx.table.header 等形式暴露。

rx.table.root 支持 variantsize 两个外观 props(源码中定义于 table.py):

Prop 可选值 作用
variant "surface""ghost" 表格的视觉样式:surface 带背景与边框,ghost 为无边框幽灵样式
size "1""2""3" 表格的尺寸(影响行高与内边距)

此外 TableRow 支持 alignstart/center/end/baseline),TableCellTableColumnHeaderCell 支持 justifymin_widthmax_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",
        ),
    ),
)

添加提交按钮与事件处理器

现在字段齐全了,但还无法提交。需要两件事:

  1. 提交按钮:在 vstack 里加一个 rx.button,文本显示在按钮上,type prop 设为 "submit",点击时触发表单提交;
  2. 事件处理器:所有 state 的变更都通过 State 类中的函数完成,这些函数被称为 event handlers。组件有特殊的 props 称为事件触发器(event triggers),例如 on_submit,它们把组件连接到事件处理器上,从而更新 state。不同的事件触发器对事件处理器的签名要求不同——有的不传参,有的传特定参数。

rx.formon_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 正是表单字段的 namenameemailgender),恰好与 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_openon_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_key prop 指定数据字典中代表 y 值的 key(这里是 "value");strokefill 设置柱条描边与填充颜色;
  • rx.recharts.x_axis:x 轴。data_key 指定数据字典中代表 x 值的 key(这里是 "name");
  • rx.recharts.y_axis:y 轴;
  • widthheight 设置图表尺寸。
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_sizemax_bar_sizestack_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:应用的全局圆角值,被所有带 radius prop 的组件继承;单个组件可以手动设置 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_submiton_open_changeon_load 等事件触发器把组件与事件处理器相连,事件处理器修改 state 后 UI 自动重渲染;
  • Styling:如何调整主题(rx.themeradiusaccent_color)、布局(hstack/vstack/flex)与交互状态(hover 伪类)。

延伸阅读

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.16 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
932
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.95 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
606
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23