Reconciler¶
Diffs successive element trees and applies the smallest set of native
mutations that bring the on-screen view tree in line with the new
description. The reconciler is platform-agnostic; it talks to native
widgets exclusively through a backend such as the
NativeViewRegistry or
the in-memory FakeBackend.
Reconciler package: turns element trees into batched native mutations.
The public surface is Reconciler and
VNode; the remaining modules are
implementation detail:
core: render, diff, commit.boundaries:ErrorBoundaryandSuspense.layout_pass: the flexbox pass and frame diffing.children: minimal move planning for keyed child lists.vnode: the mounted-tree node and pure helpers.
Core¶
The Reconciler: element trees in, native mutations out.
One reconciler owns a mounted application tree or headless test render. Logical screens, overlays, and mounted list rows share that tree. Each pass runs the same phases in order:
- Render: component bodies run for the subtrees that need it,
producing
Elementdescriptions. Hook state is installed for the duration of each body. - Diff and stage: elements are compared with the mounted
VNodein their slot. Creates, prop updates, attach/detach, and destroys are staged into an op list instead of being applied immediately. - Commit: the staged ops go to the backend in one batch, refs are
populated, the layout pass runs (once a viewport is known), then
effects flush:
use_layout_effectfirst,use_effectafter.
A render requested while a pass is in flight (an effect setting state, a boundary reset) is queued and drained before the call returns, bounded so runaway update loops surface as an error rather than a hang. Hosts never observe a half-committed tree.
The class is assembled from mixins to keep each concern readable:
this module holds the render/diff/commit pipeline,
boundaries handles
ErrorBoundary and Suspense, and
layout_pass runs flexbox.
Classes:
| Name | Description |
|---|---|
Reconciler |
Owns one mounted tree and translates element diffs into native mutations. |
Reconciler
¶
Reconciler(backend: Any)
Bases: BoundaryMixin, LayoutMixin
Owns one mounted tree and translates element diffs into native mutations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
backend
|
Any
|
An object implementing the registry protocol
( |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
backend |
The backend passed at construction. |
|
on_render_requested |
Optional[Callable[[], None]]
|
Optional callback hosts set to be told a
re-render is wanted (to hop onto the UI thread or guard
with a red box). When |
on_back_registered |
Optional[Callable[[], None]]
|
Optional callback fired when the first
|
transitions |
Deferred-render queue behind
|
Methods:
| Name | Description |
|---|---|
root_view |
The root native view object, or |
mount |
Build native views for |
reconcile |
Diff |
flush_dirty |
Re-render only the components whose state changed, then commit. |
unmount |
Tear down the mounted tree, running effect cleanups and destroying native views. |
dispatch_command |
Run an imperative command against the view registered under |
dispatch_back_press |
Offer the system back action to registered handlers, newest first. |
walk |
Yield every mounted node in depth-first, document order. |
mark_dirty |
Queue |
request_render |
Ask for a flush: via the host callback when set, inline otherwise. |
register_back_handler |
Register a back-press handler; returns an unregister callable. |
set_viewport_size |
Update the viewport size and re-run layout if it changed. |
compute_layout_for_test |
Build and compute a layout tree without touching the backend. |
root_tag
property
¶
Tag of the root native view, or None when nothing is mounted.
viewport_size
property
¶
The last viewport size supplied by the host, (0, 0) before layout is possible.
mount
¶
Build native views for element and return the root native view.
Any previously mounted tree is unmounted first.
reconcile
¶
Diff element against the mounted tree and patch native views.
Pending state updates are drained as part of the pass, so hosts only call this when the root element itself changed (new props from outside the tree).
flush_dirty
¶
flush_dirty() -> Any
Re-render only the components whose state changed, then commit.
This is the hot path for state-driven updates: each dirty component re-runs its own body and reconciles its subtree in place; the batch commits as one native transaction. Returns the (possibly replaced) root native view.
unmount
¶
Tear down the mounted tree, running effect cleanups and destroying native views.
dispatch_command
¶
Run an imperative command against the view registered under tag.
dispatch_back_press
¶
dispatch_back_press() -> bool
Offer the system back action to registered handlers, newest first.
Returns True if a handler consumed the event (the platform
should not run its default behavior).
mark_dirty
¶
mark_dirty(vnode: VNode) -> None
Queue vnode (a component) for a local re-render on the next flush.
request_render
¶
Ask for a flush: via the host callback when set, inline otherwise.
register_back_handler
¶
Register a back-press handler; returns an unregister callable.
set_viewport_size
¶
Update the viewport size and re-run layout if it changed.
Called by the screen host whenever the platform reports a new container size. The first call after mount triggers the initial layout pass; identical sizes are no-ops.
compute_layout_for_test
¶
compute_layout_for_test(
viewport_width: float, viewport_height: float
) -> Optional[LayoutNode]
Build and compute a layout tree without touching the backend.
Returns the synthetic viewport LayoutNode with all
descendants positioned, or None if nothing is mounted.
Mounted nodes¶
Mounted-tree nodes and the pure helpers the reconciler shares.
VNode pairs an
Element with the identity of the native view
it produced (its tag) plus the bookkeeping the reconciler needs
across passes (layout cache, boundary state, hook state).
Everything in this module is side-effect free with respect to the native layer.
Classes:
| Name | Description |
|---|---|
VNode |
A mounted |
Functions:
| Name | Description |
|---|---|
next_tag |
Allocate a fresh, process-unique view tag. |
shallow_equal_props |
Return whether two prop dicts are equal under shallow comparison. |
normalize_children |
Normalize arbitrary render output into a flat list of Elements. |
VNode
¶
A mounted Element plus its native identity.
The reconciler walks parallel trees of VNode and incoming
Element to compute the minimal set of native mutations.
Attributes:
| Name | Type | Description |
|---|---|---|
element |
The |
|
tag |
Integer identity of the underlying native view. Native
elements own a fresh tag; transparent wrappers (components,
providers, boundaries, keyed fragments) delegate the tag of
their first native root. |
|
native_view |
Any
|
The platform-native view object, resolved from the
backend after commit. |
children |
List[VNode]
|
Ordered list of child |
parent |
Optional[VNode]
|
The owning |
hook_state |
Optional[HookState]
|
The component's
|
mounted |
bool
|
|
Methods:
| Name | Description |
|---|---|
depth |
Return the number of ancestors above this node (the root has depth 0). |
shallow_equal_props
¶
Return whether two prop dicts are equal under shallow comparison.
Used by memo to skip re-rendering when none
of a component's props changed identity. Callables only count as
equal if they're the same object; fresh closures always invalidate
the memo (matching React's behavior; pair with
use_callback when stability matters).
normalize_children
¶
Normalize arbitrary render output into a flat list of Elements.
Accepts a single element, None, True/False (both
skipped, enabling inline conditionals like cond and Text(...)),
lists/tuples/generators (flattened recursively), and unkeyed
Fragments (expanded inline so they never touch the native tree).
Keyed Fragments are preserved so they can participate in keyed
reconciliation as a unit.
Non-Element values other than the above are dropped with a dev-mode warning.
Keyed children¶
Pure planning of native child-list moves.
Given the tags a native container currently holds and the tags it
should hold after a pass, plan_child_moves
returns the minimal sequence of (tag, index) insertions (with
move-aware insert_child semantics: a child already attached is
re-positioned) that transforms one into the other.
The algorithm keeps the longest increasing subsequence of surviving
children in place and moves only the rest, processing right to left so
every moved child is inserted directly before an already-settled
neighbor. That yields n - len(LIS) operations, the optimum for a
move-aware insert primitive.
Functions:
| Name | Description |
|---|---|
longest_increasing_subsequence |
Return the indices (into |
plan_child_moves |
Compute |
longest_increasing_subsequence
¶
Return the indices (into values) of one longest strictly increasing subsequence.
plan_child_moves
¶
Compute (tag, index) insert ops turning before into after.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
before
|
Sequence[int]
|
Tags currently attached to the container, in order.
Tags absent from |
required |
after
|
Sequence[int]
|
Desired child tags, in order. Tags absent from
|
required |
Returns:
| Type | Description |
|---|---|
List[Tuple[int, int]]
|
Ordered insert operations. Applying each as "detach |
List[Tuple[int, int]]
|
attached, then insert at |
Next steps¶
- Read the high-level walkthrough in Reconciliation.
- See how native widgets are registered in Native views.