R

ReflexKR

Reflex 官方文档

组件工坊: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

组件工坊:Reflex 组件库 100 例
表单

Upload

rx.uploadrx.upload.root

官方文档

工坊订单

请在退款申请页做一个附加收据照片的区域。最多 3 张图片,拖进来就立即附上,并显示文件名和大小。

rx.upload 是一个可以拖放文件或点击选择文件的区域(拖放区)。选中的文件先留在浏览器里,把 rx.upload_files() 交给事件处理器时才会发送到服务器。服务器上的处理器收到文件列表,再读取或保存。

本课程的示例运行在公共服务器上,所以不保存文件,只显示文件名和大小。保存文件的代码另外只以代码形式展示。

rx.upload(
    rx.text("Drop files here or click to select"),
    id="upload_basic",
    border="2px dashed var(--gray-7)",
    padding="2em",
)

试着改属性

这些属性决定拖放区的行为。选几个文件后,试着关掉 multiple 或打开 no_click。选中的文件名会以徽章形式显示在下方。

试着改属性

label

multiple

disabled

no_click

no_drag

no_keyboard

rx.vstack(rx.upload(rx.text("Drop files here or click to select"), id="upload_lab", multiple=True, disabled=False, no_click=False, no_drag=False, no_keyboard=False, border="2px dashed var(--gray-7)", padding="2em"), rx.foreach(rx.selected_files("upload_lab"), lambda f: rx.badge(f, variant="soft")))
no_click 禁止点击选择,no_drag 禁止拖放,no_keyboard 禁止用 Space·Enter 打开。0.10 中 multiple 默认为 True。
属性取值默认值用途
id字符串(固定)"default"这个拖放区的名字。下面的特殊 Var·事件使用同一个 id
accept{MIME: [扩展名...]}所有文件接受的文件类型
multipleTrue / FalseTrue允许多选
max_files整数不限最多文件数
max_size, min_size字节不限单个文件的最大·最小尺寸
disabledTrue / FalseFalse停用拖放区
no_click, no_drag, no_keyboardTrue / FalseFalse禁止点击 / 拖放 / 键盘打开
drag_active_style样式 dict无文件拖入时的样式(0.10)
on_drop事件保存所选文件放下或选中文件后立即执行
on_drop_rejected事件错误提示(toast)有不符合条件的文件进来时

查看选中的文件:selected_files

选中的文件会进入浏览器端的列表。用特殊 Var rx.selected_files(id) 把文件名列表显示在页面上,用特殊事件 rx.clear_selected_files(id) 清空。此时还没有任何东西发到服务器。

id 是连接拖放区与特殊 Var·事件的钥匙,必须全部相同。请用字符串或模块常量,不能用 State 变量。拖放区里的 "Select File" 按钮只是个样子,因为在拖放区里点任何地方都会打开文件选择窗口。

rx.vstack(
    rx.upload(
        rx.vstack(
            rx.button("Select File", variant="outline"),
            rx.text("Drag and drop files here or click to select files", size="2"),
            align="center",
        ),
        id="upload_selected",
        border="1px dotted var(--indigo-8)",
        padding="2em",
    ),
    rx.hstack(rx.foreach(rx.selected_files("upload_selected"), lambda f: rx.badge(f)), wrap="wrap"),
    rx.button("Clear", on_click=rx.clear_selected_files("upload_selected"), variant="soft", color_scheme="gray"),
)

发送到服务器:rx.upload_files

把 rx.upload_files(upload_id=id) 交给处理器,选中的文件就会发到服务器。处理器是一个普通的 async 事件,接收一个 files: list[rx.UploadFile]。每个文件都可以用 file.name(原文件名)、file.size(字节)、await file.read()(内容)。

上传处理器不要加 @rx.event(background=True),也不要用 async with self。那些只用于下面的分块上传。另外,上传按钮要放在拖放区外面。放在里面的话,每次点击都会打开文件窗口,同时开始上传。

def upload_size_label(size: int) -> str:
    return f"{size / 1024:.1f} KB" if size < 1024 * 1024 else f"{size / 1024 / 1024:.2f} MB"


class UploadInfoState(rx.State):
    files: list[dict[str, str]] = []

    @rx.event
    async def handle_upload(self, files: list[rx.UploadFile]):
        # Read only the name and size. Nothing is saved to disk.
        self.files = [{"name": str(f.name), "size": upload_size_label(f.size or 0)} for f in files]


rx.vstack(
    rx.upload(
        rx.text("Drop up to 5 files (2 MB each)"),
        id="upload_info",
        max_files=5,
        max_size=2 * 1024 * 1024,
        border="2px dashed var(--gray-7)",
        padding="2em",
    ),
    rx.hstack(rx.foreach(rx.selected_files("upload_info"), lambda f: rx.badge(f, variant="soft")), wrap="wrap"),
    rx.hstack(
        rx.button("Upload", on_click=UploadInfoState.handle_upload(rx.upload_files(upload_id="upload_info"))),
        rx.button("Clear", on_click=rx.clear_selected_files("upload_info"), variant="soft", color_scheme="gray"),
    ),
    rx.foreach(
        UploadInfoState.files,
        lambda f: rx.hstack(rx.icon("file", size=14), rx.text(f["name"], size="2"), rx.code(f["size"]), align="center"),
    ),
)

保存并显示(仅代码)

实际应用中,把文件保存到 rx.get_upload_dir() 返回的文件夹(Path,默认 ./uploaded_files,可用环境变量 REFLEX_UPLOADED_FILES_DIR 修改),页面上则用 rx.get_upload_url(文件名) 生成地址,交给 rx.image 等组件。下面是官方文档的基础示例,本页不会执行它。

class State(rx.State):
    uploaded_files: list[str] = []

    @rx.event
    async def handle_upload(self, files: list[rx.UploadFile]):
        for file in files:
            data = await file.read()
            path = rx.get_upload_dir() / file.name
            with path.open("wb") as f:
                f.write(data)
            self.uploaded_files.append(file.name)


def upload_component():
    return rx.vstack(
        rx.upload(id="upload"),
        rx.button("Upload", on_click=State.handle_upload(rx.upload_files("upload"))),
        rx.foreach(
            State.uploaded_files,
            lambda f: rx.image(src=rx.get_upload_url(f), alt="Uploaded file preview"),
        ),
    )
  • rx.get_upload_dir():服务器(后端)上的保存路径,在事件处理器中使用。
  • rx.get_upload_url(name):页面(前端)上使用的地址。它不是 Python 字符串而是前端 Var,所以只在组件代码里用。地址形如 /_upload/文件名,但不要手写,要通过这个函数生成。
  • 上传文件夹通过 /_upload 对所有人公开。保存时最好加上随机前缀,避免重名。
  • 在 Reflex Cloud 这类托管环境中,每次部署都会清空上传文件夹。需要长期保存的文件请放到 S3 之类的外部存储。

一步上传:on_drop 与过滤

把处理器接到 on_drop 上,不用按钮,选中就立即上传。用 accept 过滤文件类型,用 max_files 和 max_size 过滤数量和大小。不符合条件的文件会进入 on_drop_rejected;不提供这个事件时,0.10 会弹出错误提示。在下面放一个不是图片或 PDF 的文件,或者超过 2MB 的文件试试。

class UploadDropState(rx.State):
    names: list[str] = []
    rejected: int = 0

    @rx.event
    async def handle_drop(self, files: list[rx.UploadFile]):
        self.names = [f"{f.name} ({f.size or 0} bytes)" for f in files]

    @rx.event
    def handle_rejected(self, rejections: list):
        self.rejected = len(rejections)


rx.vstack(
    rx.upload(
        rx.text("Images or PDF only, up to 3 files", size="2"),
        id="upload_drop",
        multiple=True,
        accept={
            "application/pdf": [".pdf"],
            "image/png": [".png"],
            "image/jpeg": [".jpg", ".jpeg"],
            "image/webp": [".webp"],
        },
        max_files=3,
        max_size=2 * 1024 * 1024,
        no_keyboard=True,
        on_drop=UploadDropState.handle_drop(rx.upload_files(upload_id="upload_drop")),
        on_drop_rejected=UploadDropState.handle_rejected,
        border="2px dashed var(--gray-7)",
        padding="2em",
    ),
    rx.foreach(UploadDropState.names, lambda n: rx.text(n, size="2")),
    rx.cond(UploadDropState.rejected > 0, rx.text("Rejected: ", UploadDropState.rejected, size="2", color_scheme="red")),
)

不带样式的拖放区:rx.upload.root

rx.upload 是带默认边框和内边距的拖放区。想从头自己做外观,就用 rx.upload.root。属性和事件都一样。用 drag_active_style 可以改变文件拖入期间的背景。

rx.vstack(
    rx.upload.root(
        rx.box(
            rx.icon("cloud-upload", size=40, color=rx.color("indigo", 9)),
            rx.hstack(rx.text("Click to upload", weight="bold", color=rx.color("indigo", 11)), rx.text("or drag and drop"), size="2"),
            rx.text("SVG, PNG, JPG or GIF (MAX. 5MB)", size="1", color=rx.color("gray", 10)),
            display="flex",
            flex_direction="column",
            align_items="center",
            gap="6px",
            padding="1.5rem",
            text_align="center",
        ),
        id="upload_root_demo",
        max_width="24rem",
        border=f"2px dashed {rx.color('indigo', 7)}",
        border_radius="12px",
        cursor="pointer",
        drag_active_style={"background_color": rx.color("indigo", 3)},
    ),
    rx.hstack(rx.foreach(rx.selected_files("upload_root_demo"), lambda f: rx.badge(f)), wrap="wrap"),
)

进度与取消

rx.upload_files(以及 rx.upload_files_chunk)接受 on_upload_progress。上传期间,{"loaded": 已发送字节, "total": 总字节, "progress": 0~1} 这样的 dict 会传给这个处理器。rx.cancel_upload(id) 会中止正在进行的上传。本页只接受 2MB 以内的文件,所以进度很快就到 100。

Total bytes received: 0

class UploadProgressState(rx.State):
    uploading: bool = False
    progress: int = 0
    total_bytes: int = 0

    @rx.event
    async def handle_upload(self, files: list[rx.UploadFile]):
        self.total_bytes = sum(f.size or 0 for f in files)

    @rx.event
    def handle_upload_progress(self, progress: dict):
        self.uploading = True
        self.progress = round(progress["progress"] * 100)
        if self.progress >= 100:
            self.uploading = False

    @rx.event
    def cancel_upload(self):
        self.uploading = False
        return rx.cancel_upload("upload_progress")


rx.vstack(
    rx.upload(
        rx.text("Drag and drop files here or click to select files", size="2"),
        id="upload_progress",
        max_size=2 * 1024 * 1024,
        border="1px dotted var(--indigo-8)",
        padding="2em",
    ),
    rx.vstack(rx.foreach(rx.selected_files("upload_progress"), lambda f: rx.text(f, size="2"))),
    rx.progress(value=UploadProgressState.progress, max=100),
    rx.cond(
        ~UploadProgressState.uploading,
        rx.button(
            "Upload",
            on_click=UploadProgressState.handle_upload(
                rx.upload_files(
                    upload_id="upload_progress",
                    on_upload_progress=UploadProgressState.handle_upload_progress,
                ),
            ),
        ),
        rx.button("Cancel", on_click=UploadProgressState.cancel_upload, color_scheme="red"),
    ),
    rx.text("Total bytes received: ", UploadProgressState.total_bytes, size="2"),
    width="100%",
)

大文件:分块上传(仅代码)

await file.read() 会把整个文件读进内存。大文件用 rx.upload_files_chunk(upload_id=...) 一块一块接收,直接写入磁盘。分块处理器必须是 @rx.event(background=True),接收 chunk_iter: rx.UploadChunkIterator 并且必须读到最后(中途返回会导致上传失败)。每个分块都有 chunk.filename、chunk.offset、chunk.content_type、chunk.data。这段代码会写磁盘,所以这里不执行。

class ChunkUploadState(rx.State):
    uploaded_files: list[str] = []

    @rx.event(background=True)
    async def handle_large_upload(self, chunk_iter: rx.UploadChunkIterator):
        file_handles = {}
        try:
            async for chunk in chunk_iter:  # must consume every chunk
                fh = file_handles.get(chunk.filename)
                if fh is None:
                    path = rx.get_upload_dir() / "stream" / chunk.filename
                    path.parent.mkdir(parents=True, exist_ok=True)
                    fh = file_handles[chunk.filename] = path.open("wb")
                fh.seek(chunk.offset)
                fh.write(chunk.data)
        finally:
            for fh in file_handles.values():
                fh.close()
        async with self:
            self.uploaded_files = sorted(file_handles)


# rx.button("Upload Large Files",
#           on_click=ChunkUploadState.handle_large_upload(rx.upload_files_chunk(upload_id="large_upload")))

表单中的上传

表单的 on_submit 不包含文件。如果表单里有拖放区,文件要通过 type="button" 的按钮或 on_drop 调用 rx.upload_files() 单独发送,其余输入再用 on_submit 接收。

完成订单

拖放区先过滤图片类型、数量和大小,on_drop 让文件一放下服务器就收到文件名和大小。列表只保留最近三个。不做保存。

Receipt photos (max 3, 2 MB each)

class UploadReceiptState(rx.State):
    files: list[dict[str, str]] = []

    @rx.event
    async def attach(self, files: list[rx.UploadFile]):
        added = [{"name": str(f.name), "size": f"{(f.size or 0) / 1024:.0f} KB"} for f in files]
        self.files = (self.files + added)[-3:]

    @rx.event
    def clear(self):
        self.files = []


rx.card(
    rx.vstack(
        rx.text("Receipt photos (max 3, 2 MB each)", weight="bold", size="2"),
        rx.upload(
            rx.hstack(rx.icon("image-plus", size=18), rx.text("Drop or click to attach", size="2"), align="center"),
            id="upload_receipt",
            accept={"image/png": [".png"], "image/jpeg": [".jpg", ".jpeg"]},
            max_files=3,
            max_size=2 * 1024 * 1024,
            on_drop=UploadReceiptState.attach(rx.upload_files(upload_id="upload_receipt")),
            border="1px dashed var(--gray-8)",
            border_radius="8px",
            padding="1em",
            width="100%",
            drag_active_style={"background_color": "var(--accent-3)"},
        ),
        rx.foreach(
            UploadReceiptState.files,
            lambda f: rx.hstack(rx.icon("image", size=14), rx.text(f["name"], size="2"), rx.spacer(), rx.code(f["size"]), width="100%", align="center"),
        ),
        rx.button("Remove all", on_click=UploadReceiptState.clear, size="1", variant="soft", color_scheme="gray"),
        width="100%",
    ),
    width="100%",
    max_width="380px",
)

工坊笔记

  • 用 rx.upload(id="固定 id") 创建拖放区。选中的文件用 rx.selected_files(id),清空用 rx.clear_selected_files(id)。

  • 发送靠处理器(rx.upload_files(upload_id=id))。处理器是 async,参数为 files: list[rx.UploadFile]。

  • 用 on_drop 时选中即上传。用 accept · max_files · max_size 过滤,被拒的进入 on_drop_rejected。

  • 保存用 rx.get_upload_dir(),显示用 rx.get_upload_url()。上传文件夹是公开的。

  • 上传按钮放在拖放区外面。大文件用 rx.upload_files_chunk + background 处理器。

← 上一课

表单 · Form

下一课 →

数据展示 · Badge

订阅新课时和新课程通知

本课程新增课时或推出后续课程时,我们会发邮件通知你。

收集项目:电子邮箱 · 用途:本课程的新课时和上线通知 · 保存期限:直到你要求删除。不会用于其他用途,也不会提供给他人。回复任意通知邮件,我们会立即删除。

问答

关于本课有任何疑问都可以在这里留言。所有语言版本页面的提问会汇集在一起。

以纯文本显示,不支持链接和格式。

R

ReflexKR

动手学 Python 和 Reflex。所有示例均已在 Reflex 0.10.0 上实际运行验证。

ReflexKR 免费,没有横幅广告。如果对你有帮助,欢迎请我们喝杯咖啡。 ☕ 请我们喝杯咖啡

联盟链接与赞助说明
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex