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
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
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.
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.
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.
| Anotación | Valores | Por defecto | Uso |
|---|---|---|---|
rx.Var[T] | str, int, TypedDict … | Ninguno | Propiedad normal. Dentro de la función se trata como Var |
rx.RestProp | Como mucho un parámetro | Ninguno | Pasa tal cual el resto de propiedades no declaradas (el ...rest de JSX) |
children: rx.Var[rx.Component] | El nombre debe ser children | Ninguno | Los componentes hijos del lado que llama |
| Valor por defecto | rx.EMPTY_VAR_STR rx.EMPTY_VAR_INT rx.EMPTY_VAR_COMPONENT | Ninguno | El valor por defecto también debe ser un Var. Cadena vacía, 0, componente vacío |
| Llamada | memo_fn(title="...") | Las propiedades solo se pasan por nombre (palabra clave) | |
wrapper= | Var o None | El memo de React | Función JavaScript que envuelve |
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"),
)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",
)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.
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")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.
Body copy goes here.
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",
)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",
)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)Ú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.
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: ...| Argumento | Tipo | Uso |
|---|---|---|
component_fn | Callable[..., rx.Component | rx.Var] | Función que se convierte en memo. Todos los parámetros son rx.Var[...] o rx.RestProp |
wrapper | rx.Var | None | Funció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).
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.
Desk lamp
$39Notebook
$6Fountain pen
$58from 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.
Recibe avisos de nuevas lecciones
Te avisaremos por correo cuando este curso tenga lecciones nuevas o se abra un curso de continuación.
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© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
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.