Component
wybthon.component¶
component
¶
The @component decorator and the Component type it produces.
Wybthon components run once: the body executes a single time when
the component mounts and returns a tree. Inputs are declared on a
Props class and arrive as one typed parameter, the
counterpart of Solid's props object:
class CounterProps(Props):
label: Prop[str] = prop(default="Count")
initial: Prop[int] = prop(default=0)
@component
def Counter(props: CounterProps):
count, set_count = create_signal(props.initial.peek())
return div(
p(props.label, ": ", count),
button("+", on_click=lambda: set_count(lambda n: n + 1)),
)
render(Counter(initial=10), "#app")
Calling a component with keyword props returns a node. Type checkers see the call as constructing the props class, so missing, mistyped, and unknown props are errors without a plugin. A component with no inputs takes no parameters.
Children are the children prop. Pass them as a keyword, or use item
syntax on the call: Card(title="Hi")[h2("Body"), p("More")]. Declare
them with ParentProps.
Classes:
| Name | Description |
|---|---|
Component |
A run-once component produced by |
Functions:
| Name | Description |
|---|---|
component |
Declare a function as a run-once Wybthon component. |
Component
¶
component
¶
Declare a function as a run-once Wybthon component.
The function takes no parameters, or one parameter annotated with a
Props subclass. Calling the result returns a node
for the tree.
Static typing: the decorated object is typed as the props class (or
as a no-argument callable), so a type checker validates each call's
keywords against the declared fields. At run time it's a
Component that returns a VNode.
What's in this module¶
The @component decorator turns a function into a
run-once Component. The body executes a single
time when the component mounts and returns a tree. Inputs are declared
on a Props subclass and arrive as one typed
parameter; later prop changes flow into its accessors without re-running
the body. Calling a component with keyword arguments returns a VNode,
so trees compose like any other element.
| Name | Description |
|---|---|
component |
Decorator producing a Component from a function with one Props-annotated parameter, or none. |
Component |
Callable wrapper; Counter(label="Clicks") returns a VNode. Type checkers see the call as constructing the props class. |
Declaring props¶
Prop[T]fields are reactive. The parent may pass a value, an accessor, or a zero-argument function, and readingprops.labelreturns anAccessor. Declare defaults withprop(default=...)orprop(default_factory=...); checkers only see the default when it's passed by keyword.- Plain fields (any other annotation) hold data the parent passes through untouched, such as callbacks. Reading one returns the value as passed and never calls it.
ParentPropsdeclareschildren. Pass children as thechildrenkeyword, or with item syntax:Card(title="Hi")[p("a"), p("b")].key=is accepted by every component.- A component with no inputs takes no parameters.
In dev mode, an unknown prop or a missing required one raises
TypeError at the call site. There's no **rest; to forward
attributes, spread a merge or omit
view onto an element.
from collections.abc import Callable
from wybthon import ParentProps, Prop, Props, button, component, create_signal, div, h2, omit, p, prop
class CounterProps(Props):
label: Prop[str] = prop(default="Count")
initial: Prop[int] = prop(default=0)
on_change: Callable[[int], None] | None = None # plain field: never called by the read
@component
def Counter(props: CounterProps):
count, set_count = create_signal(props.initial.peek()) # one-time read: use .peek()
def increment():
set_count(lambda n: n + 1)
if props.on_change is not None:
props.on_change(count.peek() + 1)
return div(
p(props.label, ": ", count), # accessors placed in the tree become holes
button("+", on_click=increment),
)
class CardProps(ParentProps):
title: Prop[str]
class_: Prop[str] = prop(default="card")
@component
def Card(props: CardProps):
return div(h2(props.title), props.children, **omit(props, "title", "children"))
Counter(initial=5, label="Clicks", on_change=print)
Card(title=lambda: heading())[p("body text")] # accessors stay live
Reading a prop or signal at the top level of the body freezes the value
and warns in dev mode; call it inside a hole, memo, or effect, or make
the one-time read explicit with .peek().