
View Builder Syntax Without Macros
If you are completely satisfied with the
<span>view!</span>macro syntax described earlier, you can skip this chapter. The builder syntax described in this section is always available but is never mandatory.
For various reasons, many developers prefer to avoid using macros. Perhaps you dislike the limited <span>rustfmt</span> support (though you should check out <span>leptosfmt</span>, which is a fantastic tool!). Maybe you are concerned about the impact of macros on compile time. You might prefer the aesthetic of pure Rust syntax, or you may find it challenging to switch contexts between HTML-like syntax and Rust code. Or perhaps you want greater flexibility in creating and manipulating HTML elements than what the <span>view</span> macro provides.
If you fall into any of the above categories, then the builder syntax might be suitable for you.
<span>view</span> macros expand HTML-like syntax into a series of Rust function and method calls. If you do not want to use the <span>view</span> macro, you can simply use that expanded syntax directly. And it is actually quite nice!
First, if you wish, you can even omit the <span>#[component]</span> macro: a component is simply a setup function for creating views, so you can define a component as a simple function call:
pub fn counter(initial_value: i32, step: u32) -> impl IntoView { }
Elements are created by calling functions that have the same name as the HTML elements:
p()
Custom elements/Web components can be created by using the <span>custom()</span> function and specifying their name:
custom("my-custom-element")
You can add child nodes to elements using the <span>.child()</span> method, which accepts a single child node or a tuple or array that implements the <span>IntoView</span> type.
p().child((em().child("Big, "), strong().child("bold "), "text"))
Attributes are added using the <span>.attr()</span> method. It can accept the same types you can pass as attributes to the <span>view</span> macro (i.e., types that implement <span>Attribute</span>).
p().attr("id", "foo") .attr("data-count", move || count.get().to_string())
They can also be added using attribute methods that correspond to any built-in HTML attribute names:
p().id("foo") .attr("data-count", move || count.get().to_string())
Similarly, <span>class:</span>, <span>prop:</span>, and <span>style:</span> syntax directly correspond to <span>.class()</span>, <span>.prop()</span>, and <span>.style()</span> methods.
Event listeners can be added using the <span>.on()</span> method. Strongly typed events found in <span>leptos::ev</span> can prevent typos in event names and allow for correct type inference in callback functions.
button() .on(ev::click, move |_| set_count.set(0)) .child("Clear")
If you like this style, all of these together form a very Rust-like syntax for building fully functional views.
/// A simple counter view.// A component is actually just a function call: it runs once to create the DOM and the reactive systempub fn counter(initial_value: i32, step: i32) -> impl IntoView { let (count, set_count) = signal(initial_value); div().child(( button() // Strongly typed events found in leptos::ev // 1) Prevents typos in event names // 2) Allows for correct type inference in callbacks .on(ev::click, move |_| set_count.set(0)) .child("Clear"), button() .on(ev::click, move |_| *set_count.write() -= step) .child("-1"), span().child(("Value: ", move || count.get(), "!")), button() .on(ev::click, move |_| *set_count.write() += step) .child("+1"), ))}
Using Components in Builder Syntax
To create your own components using builder syntax, you can simply use regular functions (as described above). To use other components (such as the built-in <span>For</span> or <span>Show</span> control flow components), you can leverage the fact that each component is a function that accepts a component property parameter, and component properties have their own builders.
You can use component property builders:
use leptos::html::p;
let (value, set_value) = signal(0);
Show( ShowProps::builder() .when(move || value.get() > 5) .fallback(|| p().child("I will appear if `value` is 5 or lower")) .children(ToChildren::to_children(|| { p().child("I will appear if `value` is above 5") })) .build(),)
Or you can directly construct the property struct:
use leptos::html::p;
let (value, set_value) = signal(0);
Show(ShowProps { when: move || value.get() > 5, fallback: (|| p().child("I will appear if `value` is 5 or lower")).into(), children: ToChildren::to_children(|| p().child("I will appear if `value` is above 5")),})
Using component builders allows various modifiers like <span>#[prop(into)]</span> to be correctly applied; when using struct syntax, we apply it by manually calling <span>.into()</span>.
Macro Expansion
<span>view</span> macro or <span>component</span> macro syntax features are not detailed here. However, Rust provides you with the tools needed to understand how any macro works behind the scenes. Specifically, the “Recursive Macro Expansion” feature of rust-analyzer allows you to expand any macro to show the generated code, while <span>cargo-expand</span> expands all macros in the project into regular Rust code. The remainder of this book will continue to use <span>view</span> macro syntax, but if you are unsure how to convert it to builder syntax, you can use these tools to explore the generated code.