-
Notifications
You must be signed in to change notification settings - Fork 0
Renderer Integration
Nestix core does not render to any platform. A renderer is a set of components that translate Nestix lifecycle and placement events into host operations.
This page describes the renderer contract using DOM-like examples. The same pattern applies to other host targets.
A root component should provide the top-level host handle and render its children.
#[props(debug)]
#[derive(Debug)]
pub struct RootProps {
#[props(default)]
children: Layout,
}
#[component]
pub fn Root(props: &RootProps, element: &Element) -> Element {
let root_node = find_root_node();
element.provide_handle(root_node);
layout! {
Fragment {
$(props.children.clone())
}
}
}mount_root(&layout! { Root { App } }) mounts the Nestix tree. Host children can
then find the root handle through placement.
A host component should:
- create the host object during mount;
- register placement behavior;
- synchronize reactive props with effects;
- register unmount cleanup;
- provide the host handle;
- render children, usually through
Fragment.
#[props(debug)]
#[derive(Debug)]
pub struct DivProps {
class: Option<String>,
#[props(default)]
children: Layout,
}
#[component]
pub fn Div(props: &DivProps, element: &Element) -> Element {
let node = create_div();
element.on_place(closure!([node] |placement| {
place_node(&node, placement);
}));
effect!([node, props.class] || {
set_class(&node, class.get());
});
element.on_unmount(closure!([node] || {
remove_node(&node);
}));
element.provide_handle(node.clone());
layout! {
Fragment {
$(props.children.clone())
}
}
}Text nodes are usually leaf components. They still provide a handle so later siblings can be placed after them.
#[props(debug)]
#[derive(Debug)]
pub struct TextProps {
#[props(start)]
text: String,
}
#[component]
pub fn Text(props: &TextProps, element: &Element) {
let node = create_text_node(&props.text.get());
element.on_place(closure!([node] |placement| {
place_node(&node, placement);
}));
effect!([node, props.text] || {
set_text(&node, &text.get());
});
element.on_unmount(closure!([node] || {
remove_node(&node);
}));
element.provide_handle(node);
}This component returns (), because it creates a host leaf and has no Nestix
children.
Renderer placement usually follows this order:
- If
placement.predexists, insert the host node after that predecessor. - Else if
placement.parentexists, append the host node to that parent. - Else this node is at an unattached root and the renderer may do nothing.
pred and parent are Shared<dyn Any>, so the renderer must downcast to the
host types it supports.
Use callback! for user callbacks and renderer event adapters.
#[props(debug)]
#[derive(Debug)]
pub struct ButtonProps {
on_click: Option<Shared<dyn Fn()>>,
}
effect!([node, props.on_click] || {
if let Some(on_click) = on_click.get() {
attach_click_listener(&node, callback!([on_click] || on_click()));
}
});When host APIs require removing previous listeners, store listener handles in renderer-owned state and replace them inside an effect.
Renderer components should put host updates in effects:
effect!([input, props.value] || {
input.set_value(&value.get());
});Reading props.value.get() inside the effect subscribes the effect to future
changes when the prop was signal-backed.
Always remove host objects and long-lived event listeners in on_unmount.
element.on_unmount(closure!([node, listener_id] || {
remove_listener(listener_id);
remove_node(&node);
}));This keeps renderer resources aligned with the Nestix tree.