Skip to content

VNode

wybthon.vnode

vnode

Virtual node data structure and tree-building helpers.

This module defines the core VNode type and the functions used to build it (h, Fragment, memo, dynamic). It is intentionally free of browser or DOM dependencies, so VNode trees can be constructed and inspected anywhere CPython runs.

A _dynamic VNode (created via dynamic or implicitly when a zero-argument callable appears in a child position) represents a reactive hole: the reconciler wraps the getter in its own effect that updates only the corresponding DOM region when the getter's dependencies change. This is the building block for SolidJS-style "setup once, update fine-grained" rendering.

Example

Building a small subtree without a browser::

from wybthon import h, Fragment

view = h("section", {"class": "card"},
         h("h1", {}, "Hello"),
         Fragment(h("p", {}, "Body 1"), h("p", {}, "Body 2")))

Classes:

Name Description
VNode

Virtual node representing an element, text, component, or reactive hole.

Functions:

Name Description
dynamic

Create a reactive-hole VNode that re-evaluates getter on dependency changes.

is_getter

Return True when value is a zero-arg callable suitable for a reactive hole.

h

Create a VNode from a tag, props, and children.

Fragment

Group multiple children without adding an extra DOM wrapper element.

memo

Wrap a function component to skip re-mounts when its props are unchanged.

VNode

VNode(tag: Optional[Union[str, Callable[..., Any]]], props: Optional[PropsDict] = None, children: Optional[List[ChildType]] = None, key: Optional[Union[str, int]] = None)

Virtual node representing an element, text, component, or reactive hole.

Uses __slots__ for a compact memory layout and faster attribute access, which is meaningful when authoring large lists. Internal attributes (el, subtree, render_effect, component_ctx, _frag_end) are populated by the reconciler when the VNode is mounted.

Attributes:

Name Type Description
tag

Element tag name ("div"), special tag ("_text", "_dynamic", "_fragment"), or component callable.

props

Mapping of prop names to values. Event handlers, attributes, and reactive accessors all live here.

children

List of child VNode instances (or strings, before normalization).

key

Optional stable identity used for keyed list reconciliation.

to_text_vnode

to_text_vnode(value: Any) -> VNode

Convert an arbitrary value to a text VNode.

Parameters:

Name Type Description Default
value Any

Any value. None becomes the empty string; everything else is coerced via str().

required

Returns:

Type Description
VNode

A _text VNode with the stringified content stored at nodeValue.

dynamic

dynamic(getter: Callable[[], Any], *, key: Optional[Union[str, int]] = None) -> VNode

Create a reactive-hole VNode that re-evaluates getter on dependency changes.

This is the explicit form of the same machinery that wraps callable children automatically. Use it when you want to be explicit about which child is dynamic, or to attach a stable key for keyed reuse inside a fragment.

The getter may return a VNode, a str, a list of either, or None.

Parameters:

Name Type Description Default
getter Callable[[], Any]

Zero-arg callable evaluated inside its own effect. Any signal reads inside the getter become dependencies that trigger re-evaluation.

required
key Optional[Union[str, int]]

Optional stable identity used by keyed reconciliation.

None

Returns:

Type Description
VNode

A _dynamic VNode that the reconciler will mount as a reactive hole.

Example
div(dynamic(lambda: f"Hello, {name()}!"))

is_getter

is_getter(value: Any) -> bool

Return True when value is a zero-arg callable suitable for a reactive hole.

The check excludes:

  • VNode instances
  • Classes (isinstance(value, type))
  • Components and providers (marked with _wyb_component / _wyb_provider)
  • Ref objects (have a current attribute)
  • Callables that require positional arguments (e.g. event handlers taking an event object)

Parameters:

Name Type Description Default
value Any

Any value, typically a child or prop value being normalized.

required

Returns:

Type Description
bool

True if the value should be treated as a reactive getter.

flatten_children

flatten_children(items: Iterable[Any]) -> List[Any]

Flatten nested child lists into a single list, dropping None entries.

normalize_children

normalize_children(children: List[ChildType]) -> List[VNode]

Normalize a mixed list of children into a flat list of VNodes.

Per-element handling:

  • VNode: kept as-is. Fragments are flattened into the parent list.
  • Zero-arg callable: wrapped in a _dynamic VNode (reactive hole).
  • Anything else: coerced to a text VNode.

Parameters:

Name Type Description Default
children List[ChildType]

Children as produced by h(...) or component bodies.

required

Returns:

Type Description
List[VNode]

A flat list of VNode instances ready for the reconciler.

h

h(tag: Optional[Union[str, Callable[..., Any]]], props: Optional[PropsDict] = None, *children: Any) -> VNode

Create a VNode from a tag, props, and children.

This is the low-level VNode constructor used everywhere. For common HTML tags, prefer the helpers in wybthon.html (div, span, button, …).

Callable children (zero-argument getters) are passed through unchanged; normalize_children wraps them as _dynamic VNodes when the parent element mounts. Components receive their children verbatim via the children prop so they can decide how to render them.

Parameters:

Name Type Description Default
tag Optional[Union[str, Callable[..., Any]]]

An HTML tag name ("div"), a special tag ("_text", "_dynamic", "_fragment"), or a component callable.

required
props Optional[PropsDict]

Mapping of prop names to values. May be None.

None
*children Any

Children to attach. Lists/tuples are flattened.

()

Returns:

Type Description
VNode

A new VNode.

Example
from wybthon import h

view = h("button", {"on_click": handle_click}, "Click me")

Fragment

Fragment(*args: Any) -> VNode

Group multiple children without adding an extra DOM wrapper element.

Fragments use empty comment nodes as start/end markers and mount their children directly into the parent container. This avoids extra elements that would pollute selectors like :first-child or affect layout.

Parameters:

Name Type Description Default
*args Any

Either a sequence of children (Fragment(a, b, c)) or a single dict containing a children key (the form used when Fragment is called as h(Fragment, {}, a, b, c)).

()

Returns:

Type Description
VNode

A _fragment VNode that the reconciler will mount inline.

Example
Fragment(h1("Title"), p("Body text"))
h(Fragment, {}, h1("Title"), p("Body text"))  # same thing

memo

memo(component: Callable[..., Any], are_props_equal: Optional[Callable[[PropsDict, PropsDict], bool]] = None) -> Callable[..., Any]

Wrap a function component to skip re-mounts when its props are unchanged.

Because Wybthon component bodies run once, memo is only useful when you want to skip re-mounting on a prop change (for example, components with an expensive setup phase). Most ordinary components do not need it; fine-grained holes already minimise DOM work.

Parameters:

Name Type Description Default
component Callable[..., Any]

The component callable to memoize.

required
are_props_equal Optional[Callable[[PropsDict, PropsDict], bool]]

Optional (old_props, new_props) -> bool comparator. Defaults to a shallow identity check (is) across every key.

None

Returns:

Type Description
Callable[..., Any]

A wrapped component callable. Identity is preserved across

Callable[..., Any]

renders so the reconciler can detect "same component, same

Callable[..., Any]

props".

Example
MemoList = memo(ExpensiveList,
                are_props_equal=lambda a, b: a["items"] == b["items"])

What's in this module

vnode defines the framework's lightweight virtual DOM node type along with a few pure helpers for marker nodes (Fragment) and reactive holes (dynamic, is_getter).

A VNode is just a Python object with three properties:

Field Description
tag A string tag ("div", "button"), a component callable, or a special marker.
props A dict of attributes, event handlers, and reserved props (children, key, ref).
children A list of child VNodes, primitives, or callables for reactive holes.

You usually create VNodes via h or the helpers in wybthon.html rather than instantiating VNode directly.

See also