R

ReflexKR

Documentación de Reflex

El taller de componentes: los 100 componentes de Reflex

·

Resumen del curso

Diseño

·

Box

·

Flex

·

Stack

·

Grid

·

Container

·

Section

·

Center

·

Spacer

·

Separator

·

Card

·

Inset

·

Aspect Ratio

·

Fragment

Tipografía

·

Text

·

Heading

·

Link

·

Code

·

Markdown

·

Blockquote

·

Quote

·

Strong

·

Em

·

Kbd

Formularios

·

Button

·

Input

·

Text Area

·

Checkbox

·

Switch

·

Radio Group

·

Select

·

Slider

·

Form

·

Upload

Visualización de datos

·

Badge

·

Avatar

·

Icon

·

Callout

·

List

·

Data List

·

Code Block

·

Progress

·

Spinner

·

Scroll Area

·

Moment

Mostrar y ocultar

·

Tabs

·

Accordion

·

Segmented Control

Superposiciones

·

Tooltip

·

Popover

·

Hover Card

·

Dialog

·

Alert Dialog

·

Drawer

·

Dropdown Menu

·

Context Menu

·

Toast

Renderizado dinámico

·

Cond

·

Match

·

Foreach

·

Auto Scroll

Multimedia

·

Image

·

Audio

·

Video

Tablas y cuadrículas de datos

·

Table

·

Data Table

·

Data Editor

Gráficos

·

Area Chart

·

Bar Chart

·

Line Chart

·

Composed Chart

·

Pie Chart

·

Radar Chart

·

Radial Bar Chart

·

Scatter Chart

·

Funnel Chart

·

Treemap

·

Sankey Chart

·

Error Bar

Piezas de gráficos

·

Axis

·

Cartesian Grid

·

Chart Tooltip

·

Legend

·

Label

·

Reference

·

Brush

Otras bibliotecas de gráficos

·

Plotly

·

Pyplot

Elementos HTML

·

HTML

·

HTML Text

·

HTML Layout

·

HTML Forms

·

HTML Media

·

HTML Tables

·

SVG

Otras herramientas

·

Theme

·

Skeleton

·

Clipboard

·

Html Embed

·

Script

·

Memo

El taller de componentes: los 100 componentes de Reflex
Otras herramientas

Memo

rx.memorx.RestProprx.EMPTY_VAR_STR

Documentación oficial

Pedido del taller

Separa las tarjetas de producto para que, cada vez que cambie el número del carrito, no se vuelva a dibujar también la lista de tarjetas de al lado.

@rx.memo convierte una función en un componente React memoizado. El compilador exporta esa función como un módulo aparte, y el memo de React solo la vuelve a dibujar cuando cambian las propiedades declaradas. Se usa en subárboles cuyo dibujo es costoso y que solo dependen de una parte pequeña del estado. Todas las funciones @rx.memo de esta página están definidas en la parte superior del código de ejemplo (el código del 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 solo se vuelve a dibujar cuando cambia label. Aunque subas count con Increment, este componente no se redibuja.

Valores del State como propiedades

rx.memo no tiene propiedades visibles, así que en lugar de un banco de pruebas lo vemos con ejemplos que se mueven con el State. A las propiedades se les pueden pasar tal cual Var del State, y cuando ese Var cambia, se redibuja.

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),
)

Aunque ates el State a una propiedad en el lugar de la llamada, el State no se arrastra a la página. El compilador traslada esa llamada a un componente envoltorio generado automáticamente, y es el envoltorio el que tiene los hooks de State necesarios. Cuando el State cambia, el envoltorio se redibuja, pero si el valor de la propiedad no ha cambiado de verdad, el memo de React se detiene ahí. El truco está en pasar al componente costoso solo los Var imprescindibles.

Reglas

AnotaciónValoresPor defectoUso
rx.Var[T]str, int, TypedDict …NingunoPropiedad normal. Dentro de la función se trata como Var
rx.RestPropComo mucho un parámetroNingunoPasa tal cual el resto de propiedades no declaradas (el ...rest de JSX)
children: rx.Var[rx.Component]El nombre debe ser childrenNingunoLos componentes hijos del lado que llama
Valor por defectorx.EMPTY_VAR_STR rx.EMPTY_VAR_INT rx.EMPTY_VAR_COMPONENTNingunoEl valor por defecto también debe ser un Var. Cadena vacía, 0, componente vacío
Llamadamemo_fn(title="...")Las propiedades solo se pasan por nombre (palabra clave)
wrapper=Var o NoneEl memo de ReactFunción JavaScript que envuelve

Con rx.foreach

Para dibujar un componente memo por cada elemento de una lista, envuelve la llamada en una lambda y pasa las propiedades como palabras clave. Pasa también la key que distingue cada elemento. Para que key llegue al elemento real, declara un parámetro rx.RestProp y despliégalo en el componente que devuelves. No declares key directamente en la firma. El task de dentro de la función no es un dict sino un Var, así que puedes sacar valores como task["name"], pero no puedes recorrerlo ni llamar a métodos de dict como .keys().

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"),
)

Pasar propiedades con rx.RestProp

rx.RestProp recibe las propiedades no declaradas y las pasa tal cual. Al crear un envoltorio fino que solo da estilo a un componente básico, no hace falta volver a declarar las propiedades una a una. Trata rest como un valor opaco y pásalo como argumento posicional al componente que lo usa. on_click, id y title llegan todos al botón a través de 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",
)

Con .merge puedes combinar rest con otro dict, y con rest.get(...) puedes sacar un valor para operar con Var. Eso sí, el valor no se conoce al compilar, así que no se puede ramificar con un if de Python. Si en Reflex 0.10 usas tal cual el ejemplo de la documentación oficial, hay dos cosas que no encajan. rest contiene los nombres de JavaScript que usa el navegador (className), así que no se puede sacar con "class_name", y el resultado de rest.get(...) es un Var sin tipo, así que hacer directamente + " ..." da error. Por eso se saca y se combina con "className", y se convierte en Var de cadena con .to(str) antes de sumar.

@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",
)

Límite: propiedades que se usan al construir

rx.RestProp pasa las propiedades en ejecución, así que no puede transportar valores que el create() del componente destino usa al construir. Es el caso de is_external de rx.link, tag de rx.icon o las palabras clave que recibe un create() propio. Si los mandas por rest, se quedan en un atributo normal o en CSS y no tienen ningún efecto. Esos valores se reciben como un parámetro rx.Var[...] aparte y se pasan directamente a create() en el cuerpo de la función.

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")

Recibir hijos

El parámetro que se llama children y está anotado como rx.Var[rx.Component] recibe los hijos del lado que llama. Los hijos se pasan como argumentos posicionales y el resto de propiedades como palabras clave. Las propiedades cuyo valor por defecto es rx.EMPTY_VAR_STR, como note, se pueden omitir.

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",
)

Devolver un Var en lugar de un componente

Si anotas el retorno como rx.Var[T], no se crea un componente sino una función JavaScript normal, y el lugar de la llamada pasa a ser un Var que se puede insertar en la página. El cuerpo de este tipo de memo se ejecuta una vez al compilar, así que solo admite operaciones con Var. Ni hooks ni ramificaciones de Python que dependan de un Var.

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",
)

Cambiar el envoltorio de JavaScript

El componente función compilado se envuelve por defecto con el memo de React. Si das a wrapper= otro Var invocable, se envuelve con ese, y con wrapper=None se exporta tal cual el componente función sin envolver. Abajo hay un ejemplo de wrapper=None. Solo vale para memos que devuelven componentes; en un memo que devuelve un Var se rechaza.

@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",
)

Para envolver con una función de otra biblioteca, se pasa el import dentro de un FunctionStringVar. El ejemplo de abajo necesita el paquete mobx-react-lite, así que se muestra solo como código.

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)

Cuándo usarlo y cuándo no

Úsalo cuando el dibujo es costoso, el resultado depende de pocas propiedades y un ancestro que cambia a menudo provoca redibujados innecesarios. No lo uses cuando el componente es ligero y la contabilidad sale más cara, o cuando las propiedades cambian de todos modos cada vez y memo nunca llega a saltarse nada.

Migrar desde el rx.memo antiguo

El antiguo rx.memo aceptaba anotaciones de tipo normales como title: str. Ahora hace falta rx.Var[...]. El nombre antiguo rx._x.memo redirige al nuevo rx.memo y muestra un aviso una vez.

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


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

API

ArgumentoTipoUso
component_fnCallable[..., rx.Component | rx.Var]Función que se convierte en memo. Todos los parámetros son rx.Var[...] o rx.RestProp
wrapperrx.Var | NoneFunción JS que envuelve. Por defecto, el memo de React; con None, sin envoltorio. Solo para memos que devuelven componentes

Se usa directamente, como rx.memo(fn), o se crea un decorador dando solo palabras clave, como rx.memo(wrapper=...)(fn).

Completar el pedido

Las tarjetas de producto se separan con @rx.memo. La tarjeta solo recibe product, así que aunque Add one cambie el número del carrito, las tarjetas no se redibujan. width="100%" llega a la tarjeta a través de 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",
)

Notas del taller

  • @rx.memo crea un componente React que solo se redibuja cuando cambian las propiedades declaradas.

  • Todos los parámetros son rx.Var[T] o rx.RestProp (como mucho uno). Los hijos, children: rx.Var[rx.Component].

  • Llamadas con palabras clave. Valores por defecto como Var, por ejemplo rx.EMPTY_VAR_STR. En foreach, key por rest.

  • rest solo lleva propiedades que se pasan en ejecución. Los valores que create() usa al construir, como parámetros aparte.

  • Con -> rx.Var[T] se convierte en una función JS. wrapper=None no lo envuelve con memo.

← Anterior

Otras herramientas · Script

Recibe avisos de nuevas lecciones

Te avisaremos por correo cuando este curso tenga lecciones nuevas o se abra un curso de continuación.

Datos: tu correo electrónico · Finalidad: avisos de nuevas lecciones o de la publicación de este curso · Conservación: hasta que pidas que lo borremos. No lo usamos para nada más ni lo compartimos. Responde a cualquier aviso y lo borraremos enseguida.

Preguntas y respuestas

Pregunta lo que quieras sobre esta lección. Aquí se reúnen las preguntas de todas las versiones de idioma de esta página.

Se muestra como texto plano, sin enlaces ni formato.

R

ReflexKR

Aprende Python y Reflex haciendo. Todos los ejemplos se ejecutaron y verificaron en Reflex 0.10.0.

ReflexKR es gratis y sin banners publicitarios. Si te ayudó, puedes apoyarlo con un café. ☕ Invítanos un café

Aviso de afiliados
한국어English日本語简体中文EspañolDeutsch

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

Built with Reflex