Die Komponentenwerkstatt: Reflex' 100 Bibliotheksbausteine
·
Kursüberblick
Layout
·
Box
·
Flex
·
Stack
·
Grid
·
Container
·
Section
·
Center
·
Spacer
·
Separator
·
Card
·
Inset
·
Aspect Ratio
·
Fragment
Typografie
·
Text
·
Heading
·
Link
·
Code
·
Markdown
·
Blockquote
·
Quote
·
Strong
·
Em
·
Kbd
Formulare
·
Button
·
Input
·
Text Area
·
Checkbox
·
Switch
·
Radio Group
·
Select
·
Slider
·
Form
·
Upload
Datenanzeige
·
Badge
·
Avatar
·
Icon
·
Callout
·
List
·
Data List
·
Code Block
·
Progress
·
Spinner
·
Scroll Area
·
Moment
Auf- und Zuklappen
·
Tabs
·
Accordion
·
Segmented Control
Overlays
·
Tooltip
·
Popover
·
Hover Card
·
Dialog
·
Alert Dialog
·
Drawer
·
Dropdown Menu
·
Context Menu
·
Toast
Dynamisches Rendern
·
Cond
·
Match
·
Foreach
·
Auto Scroll
Medien
·
Image
·
Audio
·
Video
Tabellen und Datengrids
·
Table
·
Data Table
·
Data Editor
Diagramme
·
Area Chart
·
Bar Chart
·
Line Chart
·
Composed Chart
·
Pie Chart
·
Radar Chart
·
Radial Bar Chart
·
Scatter Chart
·
Funnel Chart
·
Treemap
·
Sankey Chart
·
Error Bar
Diagrammbausteine
·
Axis
·
Cartesian Grid
·
Chart Tooltip
·
Legend
·
Label
·
Reference
·
Brush
Andere Diagrammbibliotheken
·
Plotly
·
Pyplot
HTML-Elemente
·
HTML
·
HTML Text
·
HTML Layout
·
HTML Forms
·
HTML Media
·
HTML Tables
·
SVG
Weitere Werkzeuge
·
Theme
·
Skeleton
·
Clipboard
·
Html Embed
·
Script
·
Memo
Werkstattauftrag
Bitte lös die Produktkarten heraus, damit nicht jedes Mal die ganze Kartenliste daneben neu gezeichnet wird, wenn sich die Zahl im Warenkorb ändert.
@rx.memo macht aus einer Funktion eine memoisierte React-Komponente. Der Compiler exportiert die Funktion als eigenes Modul, und Reacts memo zeichnet sie nur neu, wenn sich deklarierte Props ändern.
Du nimmst es für Teilbäume, die teuer zu zeichnen sind und nur von einem kleinen Teil des Zustands abhängen. Alle @rx.memo-Funktionen dieser Seite sind oberhalb des Beispielcodes (in der State-Quelle) definiert.
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 wird nur neu gezeichnet, wenn sich label ändert. Erhöhst du count mit Increment, wird diese Komponente nicht neu gezeichnet.
rx.memo hat keine sichtbaren Props, daher gibt es statt eines Experimentiertischs ein Beispiel, das sich über den State bewegt. Props kannst du direkt State-Vars übergeben; ändert sich die Var, wird neu gezeichnet.
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),
)Auch wenn du an der Aufrufstelle State an Props bindest, wird der State nicht in die Seite hineingezogen. Der Compiler verlagert den Aufruf in eine automatisch erzeugte Wrapper-Komponente, und der Wrapper hält die nötigen State-Hooks.
Ändert sich der State, wird der Wrapper neu gezeichnet, doch wenn sich der Prop-Wert gar nicht geändert hat, stoppt Reacts memo dort. Der Trick: einer teuren Komponente nur die wirklich nötigen Vars übergeben.
| Annotation | Werte | Standard | Verwendung |
|---|---|---|---|
rx.Var[T] | str, int, TypedDict … | keiner | Normale Prop. In der Funktion als Var behandeln |
rx.RestProp | höchstens ein Parameter | keiner | Reicht nicht deklarierte restliche Props unverändert weiter (...rest in JSX) |
children: rx.Var[rx.Component] | Name muss children sein | keiner | Kindkomponenten des Aufrufers |
| Standardwerte | rx.EMPTY_VAR_STR rx.EMPTY_VAR_INT rx.EMPTY_VAR_COMPONENT | keiner | Auch Standardwerte müssen Vars sein. Leerer String · 0 · leere Komponente |
| Aufruf | memo_fn(title="...") | Props nur per Name (Schlüsselwort) übergeben | |
wrapper= | Var oder None | Reacts memo | JavaScript-Funktion, die die Komponente umhüllt |
Um für jeden Listeneintrag eine Memo-Komponente zu zeichnen, packst du den Aufruf in ein Lambda und übergibst die Props per Schlüsselwort. Den key, der die Einträge unterscheidet, gibst du mit.
Damit key das eigentliche Element erreicht, deklarierst du einen rx.RestProp-Parameter und breitest ihn in der zurückgegebenen Komponente aus. key direkt in der Signatur zu deklarieren ist nicht erlaubt.
task in der Funktion ist kein dict, sondern eine Var: Du kannst Werte wie task["name"] herausholen, aber nicht darüber iterieren oder dict-Methoden wie .keys() aufrufen.
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 nimmt nicht deklarierte Props entgegen und reicht sie unverändert weiter. So baust du dünne Hüllen, die einer Basiskomponente nur ein Aussehen geben, ohne jede Prop neu zu deklarieren.
Betrachte rest als undurchsichtigen Wert und übergib ihn der Zielkomponente als Positionsargument. on_click, id und title erreichen den Button alle über 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",
)Mit .merge kannst du ein weiteres dict in rest mischen, und mit rest.get(...) holst du Werte heraus, um mit Vars zu rechnen. Die Werte sind beim Kompilieren aber unbekannt, also kannst du nicht mit Pythons if verzweigen.
Übernimmst du in Reflex 0.10 das Beispiel der offiziellen Doku unverändert, passen zwei Stellen nicht. rest enthält die JavaScript-Namen aus dem Browser (className), mit "class_name" kommst du also nicht heran,
und das Ergebnis von rest.get(...) ist eine untypisierte Var, ein direktes + " ..." wirft einen Fehler. Deshalb wird mit "className" gelesen und gemischt, und vor dem Verketten macht .to(str) daraus eine String-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 reicht Props zur Laufzeit weiter und kann daher keine Werte transportieren, die create() der Zielkomponente beim Build verwendet.
Dazu gehören is_external bei rx.link, tag bei rx.icon und Schlüsselwörter, die ein selbst geschriebenes create() annimmt. Schickst du sie per rest, werden sie zu bloßen Attributen oder CSS und wirken nicht.
Solche Werte nimmst du als eigene rx.Var[...]-Parameter entgegen und übergibst sie im Funktionsrumpf direkt an create().
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")Ein Parameter namens children mit der Annotation rx.Var[rx.Component] nimmt die Kinder des Aufrufers entgegen. Kinder übergibst du als Positionsargumente, die übrigen Props als Schlüsselwörter.
Props mit dem Standardwert rx.EMPTY_VAR_STR wie note darfst du weglassen.
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",
)Mit der Rückgabe-Annotation rx.Var[T] entsteht keine Komponente, sondern eine gewöhnliche JavaScript-Funktion, und die Aufrufstelle wird zu einer Var, die du in die Seite einsetzen kannst.
Der Rumpf eines solchen Memos läuft einmal beim Kompilieren, daher sind nur Var-Operationen erlaubt. Keine Hooks und keine Python-Verzweigungen anhand von Vars.
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",
)Kompilierte Funktionskomponenten werden standardmäßig in Reacts memo gehüllt. Gibst du wrapper= eine andere aufrufbare Var, wird damit gehüllt; mit wrapper=None wird die Funktionskomponente ungehüllt exportiert.
Unten ein Beispiel für wrapper=None. Das geht nur bei Memos, die Komponenten zurückgeben; bei Memos, die Vars zurückgeben, wird es abgelehnt.
@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",
)Um mit einer Funktion aus einer anderen Bibliothek zu hüllen, gibst du einem FunctionStringVar den Import mit. Das Beispiel unten braucht das Paket mobx-react-lite und steht deshalb nur als Code da.
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)Ja: wenn das Zeichnen teuer ist, das Ergebnis von wenigen Props abhängt und ein sich häufig ändernder Vorfahre unnötiges Neuzeichnen auslöst.
Nein: bei leichten Komponenten, bei denen die Buchführung teurer ist, oder wenn sich die Props sowieso jedes Mal ändern und memo nie etwas überspringen kann.
Das frühere rx.memo nahm gewöhnliche Typannotationen wie title: str. Heute braucht es rx.Var[...]. Der alte Name rx._x.memo leitet auf das neue rx.memo weiter und gibt einmal einen Hinweis aus.
# before
@rx.memo
def card(title: str) -> rx.Component: ...
# now
@rx.memo
def card(title: rx.Var[str]) -> rx.Component: ...| Argument | Typ | Verwendung |
|---|---|---|
component_fn | Callable[..., rx.Component | rx.Var] | Die zu memoisierende Funktion. Alle Parameter sind rx.Var[...] oder rx.RestProp |
wrapper | rx.Var | None | Umhüllende JS-Funktion. Standard ist Reacts memo, None hüllt nicht. Nur bei Memos, die Komponenten zurückgeben |
Du verwendest es direkt wie rx.memo(fn) oder erzeugst mit nur Schlüsselwörtern einen Dekorator wie rx.memo(wrapper=...)(fn).
Die Produktkarte ist per @rx.memo herausgelöst. Die Karte bekommt nur product, daher wird sie nicht neu gezeichnet, wenn sich die Warenkorbzahl über Add one ändert.
width="100%" erreicht die Karte über 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",
)Werkstattnotizen
@rx.memo erzeugt eine React-Komponente, die nur neu zeichnet, wenn sich deklarierte Props ändern.
Alle Parameter sind rx.Var[T] oder rx.RestProp (höchstens einer). Kinder per children: rx.Var[rx.Component].
Aufruf per Schlüsselwort. Standardwerte sind Vars wie rx.EMPTY_VAR_STR. In foreach geht key über rest.
rest trägt nur Laufzeit-Props. Werte, die create() beim Build braucht, als eigene Parameter.
Mit -> rx.Var[T] entsteht eine JS-Funktion. wrapper=None hüllt nicht in memo.
Über neue Lektionen informiert werden
Wir schicken dir eine E-Mail, wenn dieser Kurs neue Lektionen bekommt oder ein Folgekurs erscheint.
R
ReflexKR
Python und Reflex zum Anfassen. Alle Beispiele wurden mit Reflex 0.10.0 ausgeführt und geprüft.
ReflexKR ist kostenlos und ohne Werbebanner. Wenn es dir geholfen hat, kannst du es mit einem Kaffee unterstützen. ☕ Spendier uns einen Kaffee
Hinweis zu Affiliate-Links© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Fragen & Antworten
Frag alles zu dieser Lektion. Fragen aus allen Sprachversionen dieser Seite erscheinen hier gemeinsam.