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 例
其他工具

Memo

rx.memorx.RestProprx.EMPTY_VAR_STR

官方文档

工坊订单

请把商品卡片单独拆出来,让购物车数量每次变化时,旁边的商品卡片列表不必跟着重绘。

@rx.memo 把函数变成经过记忆化(memoize)的 React 组件。编译器把这个函数导出为单独的模块,React 的 memo 只在声明过的属性变化时才重绘它。 适用于渲染开销大、只依赖状态中一小部分的子树。本页的 @rx.memo 函数都定义在示例代码的上方(State 源码部分)。

Count: 0

Memoized Component

This component only re-renders when props change.

class MemoDemoState(rx.State):
    count: int = 0

    @rx.event
    def increment(self):
        self.count += 1


@rx.memo
def memo_expensive_component(label: rx.Var[str]) -> rx.Component:
    return rx.vstack(
        rx.heading(label, as_="h2", size="4"),
        rx.text("This component only re-renders when props change."),
        rx.divider(),
    )


rx.vstack(
    rx.text("Count: ", MemoDemoState.count),
    rx.button("Increment", on_click=MemoDemoState.increment),
    memo_expensive_component(label="Memoized Component"),
)

memo_expensive_component 只在 label 变化时重绘。用 Increment 增加 count,这个组件也不会重绘。

把 State 值作为属性

rx.memo 没有可见的属性,所以这里不用实验台,而是用 State 驱动的示例来演示。属性可以直接传入 State 的 Var,这个 Var 一变就会重绘。

Hello, World

class MemoGreetState(rx.State):
    name: str = "World"

    @rx.event
    def change_name(self, value: str):
        self.name = value


@rx.memo
def memo_greeting(name: rx.Var[str]) -> rx.Component:
    return rx.heading("Hello, " + name, as_="h2", size="5")


rx.vstack(
    memo_greeting(name=MemoGreetState.name),
    rx.input(value=MemoGreetState.name, on_change=MemoGreetState.change_name),
)

即使在调用处把 State 绑定到属性上,State 也不会被拉进页面。编译器会把这个调用移到自动生成的包装组件里,由包装组件持有所需的 State hook。 State 变化时包装组件会重绘,但如果属性值实际上没变,React 的 memo 就会在那里停下。诀窍是只给开销大的组件传它真正需要的 Var。

规则

标注取值默认值用途
rx.Var[T]str, int, TypedDict …无普通属性。在函数里作为 Var 处理
rx.RestProp最多一个参数无把未声明的其余属性原样传下去(相当于 JSX 的 ...rest)
children: rx.Var[rx.Component]名字必须是 children无调用方的子组件
默认值rx.EMPTY_VAR_STR rx.EMPTY_VAR_INT rx.EMPTY_VAR_COMPONENT无默认值也必须是 Var:空字符串·0·空组件
调用memo_fn(title="...")属性只能按名字(关键字)传入
wrapper=Var 或 NoneReact 的 memo用来包裹的 JavaScript 函数

配合 rx.foreach

想为列表的每一项渲染一个 memo 组件,就把调用包进 lambda,并用关键字传属性。区分各项的 key 也要一起传。 为了让 key 落到真正的元素上,要声明一个 rx.RestProp 参数,并把它展开到返回的组件里。不要在函数签名里直接声明 key。 函数里的 task 不是 dict 而是 Var,可以像 task["name"] 这样取值,但不能遍历,也不能调用 .keys() 这类 dict 方法。

Write docs

Review PR

from typing import TypedDict


class MemoTask(TypedDict):
    id: str
    name: str


class MemoTaskState(rx.State):
    tasks: list[MemoTask] = [
        {"id": "1", "name": "Write docs"},
        {"id": "2", "name": "Review PR"},
    ]

    @rx.event
    def add_task(self):
        n = len(self.tasks) + 1
        self.tasks = [*self.tasks, {"id": str(n), "name": f"Task {n}"}][-6:]


@rx.memo
def memo_task_card(rest: rx.RestProp, *, task: rx.Var[MemoTask]) -> rx.Component:
    return rx.card(rx.text(task["name"]), rest)


rx.vstack(
    rx.foreach(
        MemoTaskState.tasks,
        lambda task: memo_task_card(task=task, key=task["id"]),
    ),
    rx.button("Add task", on_click=MemoTaskState.add_task, variant="soft"),
)

用 rx.RestProp 传递属性

rx.RestProp 接收未声明的属性并原样传下去。给基础组件套一层只改外观的薄包装时,不用把属性一个个重新声明。 把 rest 看作一个无法窥视内部的值,作为位置参数传给要用的组件。on_click、id、title 都会通过 rest 到达按钮。

@rx.memo
def memo_pill_button(rest: rx.RestProp, *, label: rx.Var[str]) -> rx.Component:
    return rx.button(label, rest, radius="full", variant="surface")


rx.flex(
    memo_pill_button(label="Save", on_click=rx.toast("Saved"), id="memo_save"),
    memo_pill_button(label="Help", on_click=rx.toast("Help is on the way"), title="Opens help"),
    spacing="3",
)

可以用 .merge 把别的 dict 合并进 rest,也可以用 rest.get(...) 取出值做 Var 运算。不过这些值在编译时无法得知,所以不能用 Python 的 if 来分支。 在 Reflex 0.10 中原样使用官方文档的示例,会有两处对不上。rest 里存的是浏览器使用的 JavaScript 名字(className),用 "class_name" 取不出来; 而 rest.get(...) 的结果是类型未定的 Var,直接 + " ..." 会报错。所以要用 "className" 取出和合并,并先用 .to(str) 转成字符串 Var 再相加。

@rx.memo
def memo_merged_button(rest: rx.RestProp, *, label: rx.Var[str]) -> rx.Component:
    class_name = rest.get("className", "").to(str) + " font-bold tracking-wide"
    return rx.button(label, rest.merge({"className": class_name}))


rx.flex(
    memo_merged_button(label="Default"),
    memo_merged_button(label="Caller class", class_name="italic"),
    spacing="3",
)

局限:构建时使用的属性

rx.RestProp 是在运行时传递属性的,所以无法携带目标组件的 create() 在构建时使用的值。 rx.link 的 is_external、rx.icon 的 tag、自己写的 create() 接收的关键字参数都属于这一类。用 rest 传过去只会变成普通属性或 CSS,没有任何效果。 这类值要另外用 rx.Var[...] 参数接收,在函数体里直接传给 create()。

P: Foo
class MemoPrefixText(rx.el.Span):
    @classmethod
    def create(cls, *children, prefix: rx.Var[str] | str = "", **props) -> rx.Component:
        return super().create(prefix, *children, **props)


# prefix is used by create(), so it is not passed through rest.
# take it as its own parameter and pass it to create() in the memo body.
@rx.memo
def memo_styled_text(rest: rx.RestProp, *, prefix: rx.Var[str]) -> rx.Component:
    return MemoPrefixText.create("Foo", rest, prefix=prefix)


memo_styled_text(prefix="P: ", class_name="italic")

接收子元素

名字为 children、标注为 rx.Var[rx.Component] 的参数接收调用方的子元素。子元素用位置参数传,其余属性用关键字传。 像 note 这样默认值是 rx.EMPTY_VAR_STR 的属性可以省略。

Memoized card

Body copy goes here.

With a note

note is optional

@rx.memo
def memo_panel(
    children: rx.Var[rx.Component],
    *,
    title: rx.Var[str],
    note: rx.Var[str] = rx.EMPTY_VAR_STR,
) -> rx.Component:
    return rx.box(
        rx.heading(title, as_="h3", size="3"),
        children,
        rx.text(note, size="1", color_scheme="gray"),
        padding="12px",
        border=f"1px solid {rx.color('gray', 6)}",
        border_radius="8px",
    )


rx.flex(
    memo_panel(rx.text("Body copy goes here."), title="Memoized card"),
    memo_panel(rx.button("Inside", size="1"), title="With a note", note="note is optional"),
    spacing="3",
    wrap="wrap",
)

返回 Var 而不是组件

把返回标注写成 rx.Var[T],生成的就不是组件,而是一个普通的 JavaScript 函数,调用处会成为可以嵌进页面的 Var。 这种 memo 的函数体只在编译时执行一次,所以只能用 Var 运算。不能用 hook,也不能根据 Var 做 Python 分支。

USD: $100

class MemoPriceState(rx.State):
    amount: int = 100
    currency: str = "USD"

    @rx.event
    def raise_price(self):
        self.amount += 10


@rx.memo
def memo_format_price(amount: rx.Var[int], currency: rx.Var[str]) -> rx.Var[str]:
    return currency.to(str) + ": $" + amount.to(str)


rx.hstack(
    rx.text(memo_format_price(amount=MemoPriceState.amount, currency=MemoPriceState.currency), weight="bold"),
    rx.button("+10", on_click=MemoPriceState.raise_price, size="1", variant="soft"),
    align="center",
)

更换 JavaScript 包装函数

编译出的函数组件默认用 React 的 memo 包裹。给 wrapper= 传入另一个可调用的 Var,就用它来包裹;wrapper=None 则直接导出不加包裹的函数组件。 下面是 wrapper=None 的例子。它只能用于返回组件的 memo,用在返回 Var 的 memo 上会被拒绝。

@rx.memo(wrapper=None)
def memo_swatch(width: rx.Var[int], height: rx.Var[int]) -> rx.Component:
    return rx.box(width=width.to_string() + "px", height=height.to_string() + "px", background=rx.color("indigo", 9), border_radius="4px")


rx.flex(
    memo_swatch(width=40, height=20),
    memo_swatch(width=80, height=20),
    memo_swatch(width=120, height=20),
    spacing="2",
    align="center",
)

用其他库的函数包裹时,要在 FunctionStringVar 里带上 import。下面的例子需要 mobx-react-lite 包,所以只以代码展示。

observer = rx.vars.FunctionStringVar(
    "observer",
    _var_data=rx.vars.VarData(imports={"mobx-react-lite": "observer"}),
)


@rx.memo(wrapper=observer)          # wrap with mobx's observer instead of React's memo
def observed_panel(label: rx.Var[str]) -> rx.Component:
    return rx.text(label)

什么时候用,什么时候不用

用:渲染开销大、结果由少数几个属性决定、又因为频繁变化的祖先组件而被白白重绘时。 不用:组件很轻,记账的开销反而更大时;或者属性反正每次都会变,memo 一次也跳不过去时。

从旧版 rx.memo 迁移

以前的 rx.memo 接受 title: str 这样的普通类型标注,现在需要 rx.Var[...]。旧名字 rx._x.memo 会转到新的 rx.memo,并打印一次提示信息。

# before
@rx.memo
def card(title: str) -> rx.Component: ...


# now
@rx.memo
def card(title: rx.Var[str]) -> rx.Component: ...

API

参数类型用途
component_fnCallable[..., rx.Component | rx.Var]要做成 memo 的函数。参数必须全是 rx.Var[...] 或 rx.RestProp
wrapperrx.Var | None用来包裹的 JS 函数。默认是 React 的 memo,None 则不包裹。仅用于返回组件的 memo

可以像 rx.memo(fn) 这样直接用,也可以像 rx.memo(wrapper=...)(fn) 这样只给关键字参数来生成装饰器。

完成订单

用 @rx.memo 把商品卡片拆了出来。卡片只接收 product,所以用 Add one 改变购物车数量时,卡片不会重绘。 width="100%" 通过 rest 到达卡片。

0

Desk lamp

$39

Notebook

$6

Fountain pen

$58
from typing import TypedDict


class MemoProduct(TypedDict):
    id: str
    name: str
    price: str


class MemoShopState(rx.State):
    cart: int = 0
    products: list[MemoProduct] = [
        {"id": "p1", "name": "Desk lamp", "price": "$39"},
        {"id": "p2", "name": "Notebook", "price": "$6"},
        {"id": "p3", "name": "Fountain pen", "price": "$58"},
    ]

    @rx.event
    def add_one(self):
        self.cart += 1

    @rx.event
    def empty_cart(self):
        self.cart = 0


@rx.memo
def memo_product_card(rest: rx.RestProp, *, product: rx.Var[MemoProduct]) -> rx.Component:
    return rx.card(
        rx.flex(
            rx.icon("package", size=18),
            rx.text(product["name"], weight="bold"),
            rx.spacer(),
            rx.badge(product["price"], variant="soft"),
            spacing="3",
            align="center",
        ),
        rest,
    )


rx.vstack(
    rx.hstack(
        rx.badge(rx.icon("shopping-cart", size=14), MemoShopState.cart, size="2"),
        rx.button("Add one", on_click=MemoShopState.add_one, size="1"),
        rx.button("Empty", on_click=MemoShopState.empty_cart, size="1", variant="soft", color_scheme="gray"),
        align="center",
    ),
    rx.foreach(
        MemoShopState.products,
        lambda p: memo_product_card(product=p, key=p["id"], width="100%"),
    ),
    width="100%",
    max_width="380px",
)

工坊笔记

  • @rx.memo 生成只在声明的属性变化时才重绘的 React 组件。

  • 参数必须全是 rx.Var[T] 或 rx.RestProp(最多一个)。子元素用 children: rx.Var[rx.Component]。

  • 调用时用关键字。默认值是 rx.EMPTY_VAR_STR 这类 Var。在 foreach 中 key 走 rest。

  • rest 只传运行时的属性。create() 在构建时使用的值要另设参数。

  • 写成 -> rx.Var[T] 就会变成 JS 函数。wrapper=None 表示不用 memo 包裹。

← 上一课

其他工具 · Script

订阅新课时和新课程通知

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

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

问答

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

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

R

ReflexKR

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

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

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

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

Built with Reflex