Until now, we learned how to create components and how to implement basic interactions for them, but to wield the full power of a component system, we need to pass props down from parent to child components. Nesting of components into each other is also crucial.
Since no Yew dev came to question my methods, I'll see this as a sign that I'm not teaching you, and, in turn, myself crap. :D
TL;DR Again, the code can be found on GitHub
Setup
I won't go into installing Rust, wasm_bindgen, or setting up a Yew project. All that can be found in the first article of this series.
Creating the Main Component
Let's start with the root component of our app. Write the following code into your src/lib.rs
file:
#![recursion_limit = "1000"]
mod list;
use list::ListGroup;
use list::ListGroupItem;
use wasm_bindgen::prelude::*;
use yew::prelude::*;
struct Model {}
impl Component for Model {
type Message = ();
type Properties = ();
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self {}
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
false
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
html! {
<ListGroup>
<ListGroupItem>{"First"}</ListGroupItem>
<ListGroupItem active=true>{"Second"}</ListGroupItem>
<ListGroupItem>{"Third"}</ListGroupItem>
</ListGroup>
}
}
}
#[wasm_bindgen(start)]
pub fn run_app() {
App::<Model>::new().mount_to_body();
}
If you read the previous article, most of that code should seem familiar, and if you have used any of the major frontend frameworks lately, even the new code in the view
method doesn't seem any special. Only the text-nodes may seem a bit out of place with their extra syntax, but somehow Rust seems to have "opinions" about strings, haha.
Anyway, at the top, we declare a new module list
.
The mod list
statement tells Rust there is a module in a file called list.rs
, which is in the same directory as the current file.
The use
statement allows us to use the public values inside that module as defined in the current file.
Creating the List Component
Next, we need to implement the components we used in the root component. For this, let's create a src/list.rs
file and insert this code:
use yew::prelude::*;
pub struct ListGroup {
props: ListGroupProps,
}
#[derive(Properties, Clone)]
pub struct ListGroupProps {
pub children: Children,
}
impl Component for ListGroup {
type Message = ();
type Properties = ListGroupProps;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self { props }
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
false
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
self.props = props;
true
}
fn view(&self) -> Html {
html! {
<ul class="list-group">{ self.props.children.clone() }</ul>
}
}
}
So what's going on here?
First, the struct that holds our components state has a props
field. This field can be used to access the props later and also to compare new props to the old ones when we want to safe performance on re-renders.
Then the struct that actually holds our props. In this case, we only accept children
as props, nothing else. The attribute above the struct definition here gives the struct some extra features that wouldn't be available in a plain struct. These features are required that Yew recognizes the struct as a props struct.
Next comes the trait implementation. We need to set the Properties
type to the props struct we defined and then implement a few methods.
The first interesting method is create
, where we save the received props
into our state.
Then the change
method, where we do the same. This time we could check if the props actually changed and return false
if they didn't. Doing so would prevent a re-render.
Finally, the view
method. Here we get our state struct as self
. Since state struct holds our props
and, in turn, our props
hold children
, we can call clone()
on them to get a copy of our children
that will be rendered into an unsorted list.
Creating the Item Component
Now we need the actual list items our list should hold. Since the list and the item component belong together, let's put them in the same file. Add this code at the bottom of the src/list.rs
file:
pub struct ListGroupItem {
props: ListGroupItemProps,
}
#[derive(Properties, Clone)]
pub struct ListGroupItemProps {
pub children: Children,
#[prop_or(false)]
pub active: bool,
}
impl Component for ListGroupItem {
type Message = ();
type Properties = ListGroupItemProps;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self { props }
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
false
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
self.props = props;
true
}
fn view(&self) -> Html {
let mut classes = vec!["list-group-item"];
if self.props.active {
classes.push("active")
}
html! {
<li class=classes>{ self.props.children.clone() }</li>
}
}
}
The item component isn't much different from the list component. It renders to a list item HTML element and passes its children along.
The only extra is an active
prop. This allows marking the component with different styling.
All props have to be marked public because the users of our components need to know what type every prop has.
With the #[prop_or(false)]
attribute we can give every prop a default value. If you forget to do this and don't pass that value, you will get bizarre error messages with nothing to do with the missing value. So "YAY" for type-systems :D
In the view
method, we use the active
prop to conditionally concatenate a vector that will serve as our CSS class definition.
Yew accepts strings, vectors, and tuples as values for class
.
Building & Testing the App
To build the app, run this command:
$ wasm-pack build --target web --out-name wasm --out-dir ./static
If you followed the instructions of the first tutorial, you should now be able to run:
$ miniserve ./static
And check out your app in the browser!
Typing Children
You're probably thinking, why can't we type the children
props more specific?
Well, we can!
Instead of using the catch-all type Children
we use the generic type ChildrenWithProps<T>
where T
is the item type we use.
So we have to refactor the props type of our list as follows:
#[derive(Properties, Clone)]
pub struct ListGroupProps {
pub children: ChildrenWithProps<ListGroupItem>,
}
This way, our list component only accepts a list of our item types as children and the compiler will warn the users if they used the wrong type.
Bonus: Binary Size Optimization
The WASM binary created is rather big; on my machine, it was ~130KB. So, I thought, let's look into some improvements here.
If you add a release profile to the bottom of the Cargo.toml
file, things should shrink a bit.
[profile.release]
panic = 'abort'
codegen-units = 1
opt-level = 'z'
lto = true
You can also swap out the memory allocator. There is one created explicitly for the WASM compile target and is smaller than the default one. This allocator is called wee_alloc
and can be installed by adding it as a dependency to your Cargo.toml
.
[dependencies]
yew = "0.17"
wasm-bindgen = "0.2.67"
wee_alloc = "0.4.2"
And activating it at the top of your src/lib.rs
file.
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
After these changes, I got the build down to ~100KB, which is a reduction of about 25%, not great, not terrible.
Conclusion
Rust still feels rather chatty, but overall at least Yew seems very familiar for a React dev like me.
Sometimes, the compiler's errors lead you in a completely false direction, but that's not often the case.
Top comments (1)
I am learning Rust and Yew now.
Very interesting language and framework.
I learned a lot from your article.
Don't be discouraged because there aren't many respondents.
It's probably because there aren't many Rust or Yew users.
In my opinion, Rust has a considerably steeper learning curve than JS.
But a steeper learning curve has one good thing.
it makes it possible to clearly differentiate between talented and unskilled developers.
You have proven your skills.
I respect the high skills of the people who wrote Rust and Yew, and you.
I will try to be involved in a professional Rust project once I have the financial motivation.
I wish you success.
David.
github.com/strict-beauty