0.7
Optional parameters
It's now possible to annotate parameters inside the #[component]
attribute macro to be optional and use a default value if omitted:
#[component(
// Make `name` an optional parameter, defaults to `"Kobold"`
name?: "Kobold",
// Make `age` an optional parameter, use the `Default` value
age?,
)]
fn Greeter<'a>(name: &'a str, age: Option<u32>) -> impl View + 'a {
let age = age.map(|age| view!(", you are "{ age }" years old"));
view! {
<p> "Hello "{ name }{ age }
}
}
view! {
// Hello Kobold
<Greeter />
// Hello Alice
<Greeter name="Alice" />
// Hello Bob, you are 42 years old
<Greeter name="Bob" age={42} />
}
Setting an optional parameter requires that the value implements the new Maybe
trait. Because Maybe<T>
is implemented for Option<T>
we can set age
to plain 42
without wrapping it in Some
in the example above.
The reverse is also true which allows for optional parameters of any type to be set using an Option
:
#[component(code?: 200)]
fn StatusCode(code: u32) -> impl View {
view! {
<p> "Status code was "{ code }
}
}
view! {
// Status code was 200
<StatusCode />
// Status code was 404
<StatusCode code={404} />
// Status code was 200
<StatusCode code={None} />
// Status code was 500
<StatusCode code={Some(500)} />
}
This is a breaking change when it comes to internals (the interaction between #[component]
and view!
macros), as components now set their fields using methods in a builder pattern. The user facing API for components as documented in 0.6 however remains compatible.
Optional parameters are a zero-cost feature, meaning if you are not using them your components will be just as fast and your compiled Wasm binary will be just as small as before (smaller actually, more on that later).
Optional closing tags for HTML elements
💡 Note: Following changes are all backwards compatible with the JSX-esque syntax view!
macro has been using thus far.
End of macro closes all tags:
view! {
// no closing tags necessary at the end of macro
<header><h1> "Hello Kobold"
}
Closing an ancestor closes all children:
view! {
<div>
<header>
<h1> "Hello Kobold"
// Closing the `div` closes both `h1` and `header`
</div>
}
view! {
// trailing `/` is mandatory for components without children
<MyComponent />
<p> "Paragraph under the component"
}
Void elements (img
, input
, etc.) have no closing tags
view! {
<div>
"This text is inside the div"
// `input` is forbidden from having children and doesn't need closing
<input type="text">
"This text is also inside the div"
}
Implicitly closing tags (li
, td
, etc.)
Some tags implicitly close other open tags as per the HTML spec, making the following legal Kobold syntax:
view! {
<ul.my-list>
// `li` closes previous `li`
<li> "Item 1"
<li> "Item 2"
<li> "Item 3"
}
view! {
<table.some-class>
<tr>
// `td` closes previous `td` or `th`
<td> "Row 1, Col 1"
<td> "Row 1, Col 2"
<td> "Row 1, Col 3"
// `tr` closes previous `td`, `th`, and/or `tr`
<tr>
<td> "Row 2, Col 1"
<td> "Row 2, Col 2"
<td> "Row 2, Col 3"
}
Async event handlers
The Hook::signal
method has been removed, as it lead to situations where you could easily shoot yourself in the foot (#56):
stateful(0, |count| {
let singal = count.signal();
// Attempting to update state inside the view always failed
// as state is currently borrowed
signal.update(|count| *count += 1);
count.get()
})
Since the only reason you'd want to have a Signal
inside the view was to create an async
even handler, Hook<T>
now has a bind_async
method that provides an owned Singal<T>
(as opposed to &mut T
in sync bind
) and handles returned future.
See the csv_editor
example:
let onload = state.bind_async(|state, event: Event<InputElement>| async move {
let file = match event.target().files().and_then(|list| list.get(0)) {
Some(file) => file,
None => return,
};
state.update(|state| state.name = file.name());
if let Ok(table) = csv::read_file(file).await {
state.update(move |state| state.table = table);
}
});
The bind!
macro currently only handles synchronous binds. This is likely to change in the next release but will require a rewrite of the macro into a procedural one.
💡 Note: You can still use the Stateful::once
method to get an access to an owned Signal
outside of the view and without invoking an event handler, see the interval
example:
#[component]
fn Elapsed(seconds: u32) -> impl View {
stateful(seconds, |seconds| {
bind! {
seconds:
let onclick = move |_| *seconds = 0;
}
view! {
<p>
"Elapsed seconds: "{ seconds }" "
// `{onclick}` here is shorthand for `onclick={onclick}`
<button {onclick}>"Reset"</button>
}
})
.once(|signal| {
// `signal` is an owned `Signal<u32>` and can be safely moved.
//
// `Interval` is returned here and will be safely dropped with the component.
Interval::new(1000, move || {
signal.update(|seconds| *seconds += 1);
})
})
}
Type hints
The view!
macro now provides type hints for all attributes in rust-analyzer, including those that are compiled away to raw JavaScript:
Internals
A lot of internals have changed, notable PRs: #63, #64, #46.
The Wasm blob in the TodoMVC example is now down to 16.98kb gzipped (was 17.16kb in 0.6 and 18.08kb in 0.5)