Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 16 additions & 20 deletions docs/advanced-topics/optimizations.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,39 @@ sidebar_label: Optimizations
description: Make your app faster
---

## neq\_assign
## Efficient rendering

When a component receives props from its parent component, the `change` method is called. This, in
addition to allowing you to update the component's state, also allows you to return a `ShouldRender`
boolean value that indicates if the component should re-render itself in response to the prop changes.
When a component receives new props from its parent component, the `changed` method is called
if the new props are not equal to the current props. This method, in addition to allowing changes
to the component's state, also allows returning a `ShouldRender` boolean value that indicates
if the component should re-render itself in response to the prop changes.

Re-rendering is expensive, and if you can avoid it, you should. As a general rule, you only want to
Re-rendering is expensive, and is best avoid if possible. As a general rule, re-renders should
only happen when the changed props affect the component's layout in the `view` method.
re-render when the props actually changed. The following block of code represents this rule, returning
`true` if the props differed from the previous props:
`true` only if the visible label property has changed and returning `false` when only `debug_value`
was changed.

```rust
use yew::ShouldRender;
use yew::{Context, ShouldRender};

#[derive(PartialEq)]
struct ExampleProps;
struct ExampleProps {
label: String,
debug_value: usize,
};

struct Example {
props: ExampleProps,
};

impl Example {
fn change(&mut self, props: ExampleProps) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
fn changed(&mut self, ctx: &Context<Self>, new_props: ExampleProps) -> ShouldRender {
return self.props.label != new_props.label
}
}
```

But we can go further! This is six lines of boilerplate can be reduced down to one by using a trait
and a blanket implementation for anything that implements `PartialEq`. Check out the [`yewtil`
crate's `NeqAssign` trait](https://docs.rs/yewtil/*/yewtil/trait.NeqAssign.html) which implements
this.

## Using smart pointers effectively

**Note: if you're unsure about some of the terms used in this section, the Rust Book has a useful
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/components/properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ fn create_default_link_color() -> LinkColor {
LinkColor::Blue
}

#[derive(Properties, Clone, PartialEq)]
#[derive(Properties, PartialEq)]
pub struct LinkProps {
/// The link must have a target.
href: String,
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/function-components/attribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ html! { <ChatContainer /> }
<!--DOCUSAURUS_CODE_TABS-->
<!--With props-->
```rust
#[derive(Properties, Clone, PartialEq)]
#[derive(Properties, PartialEq)]
pub struct RenderedAtProps {
pub time: String,
}
Expand Down
4 changes: 2 additions & 2 deletions docs/concepts/html/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ html! {
Here's the implementation of `Container`:

```rust
#[derive(Properties, Clone)]
#[derive(Properties, PartialEq)]
pub struct Props {
pub id: String,
pub children: Children,
Expand Down Expand Up @@ -90,7 +90,7 @@ html! {
```

```rust
#[derive(Properties, Clone)]
#[derive(Properties, PartialEq)]
pub struct Props {
pub children: ChildrenWithProps<ListItem>,
}
Expand Down
10 changes: 2 additions & 8 deletions docs/more/external-libs.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,12 @@ description: Libraries that can help with Yew development

Yewtil is a collection of common utilities that help you build applications using Yew. It includes:

* NeqAssign - This is described in more detail in the section on
[optimizations and best practices](../advanced-topics/optimizations.md) and ensures that identical
sets of props don't cause a component to re-render.

* PureComponents - Components that don't update any of their state. Using NeqAssign under the hood, they act as memoized
* PureComponents - Components that don't update any of their state. They act as memoized
functions that are called from inside the `html!` macro like normal components are.

* Lrc - linked list reference counted smart pointer functions like `Rc` does, but allows for novel data update patterns.
* Mrc/Irc - Mutable/Immutable reference counted smart pointers that function like `Rc` but are more ergonomic to use

within Yew, because they implement `DerefMut` and `BorrowMut`for `Mrc`. This allows `Mrc` to be used with `NeqAssign`.

within Yew, because they implement `DerefMut` and `BorrowMut`for `Mrc`.
`Irc` acts as an immutable view of the data, which makes this ideal for holding data used in display-only tasks.

* History - A history tracking wrapper that uses a `VecDeque` to hold on to previous values that it
Expand Down
73 changes: 34 additions & 39 deletions examples/boids/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use settings::Settings;
use simulation::Simulation;
use slider::Slider;
use yew::{html, Component, ComponentLink, Html, ShouldRender};
use slider::Slider as LegacySlider;
use yew::component::{Component, Context};
use yew::{html, Html, Legacy, ShouldRender};

type Slider = Legacy<LegacySlider>;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't migrate this Slider intentionally to show how a project could slowly migrate away from the legacy component trait


mod boid;
mod math;
Expand All @@ -17,25 +20,24 @@ pub enum Msg {
}

pub struct Model {
link: ComponentLink<Self>,
settings: Settings,
generation: usize,
paused: bool,
}

impl Component for Model {
type Message = Msg;
type Properties = ();

fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
fn create(_ctx: &Context<Self>) -> Self {
Self {
link,
settings: Settings::load(),
generation: 0,
paused: false,
}
}

fn update(&mut self, msg: Self::Message) -> ShouldRender {
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> ShouldRender {
match msg {
Msg::ChangeSettings(settings) => {
self.settings = settings;
Expand All @@ -58,108 +60,101 @@ impl Component for Model {
}
}

fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}

fn view(&self) -> Html {
fn view(&self, ctx: &Context<Self>) -> Html {
let Self {
ref settings,
generation,
paused,
..
} = *self;
} = self;

html! {
<>
<h1 class="title">{ "Boids" }</h1>
<Simulation settings=settings.clone() generation=generation paused=paused />
{ self.view_panel() }
{ self.view_panel(ctx) }
</>
}
}
}
impl Model {
fn view_panel(&self) -> Html {
let link = &self.link;
fn view_panel(&self, ctx: &Context<Self>) -> Html {
let pause_text = if self.paused { "Resume" } else { "Pause" };
html! {
<div class="panel">
{ self.view_settings() }
{ self.view_settings(ctx) }
<div class="panel__buttons">
<button onclick=link.callback(|_| Msg::TogglePause)>{ pause_text }</button>
<button onclick=link.callback(|_| Msg::ResetSettings)>{ "Use Defaults" }</button>
<button onclick=link.callback(|_| Msg::RestartSimulation)>{ "Restart" }</button>
<button onclick=ctx.callback(|_| Msg::TogglePause)>{ pause_text }</button>
<button onclick=ctx.callback(|_| Msg::ResetSettings)>{ "Use Defaults" }</button>
<button onclick=ctx.callback(|_| Msg::RestartSimulation)>{ "Restart" }</button>
</div>
</div>
}
}

fn view_settings(&self) -> Html {
let Self { link, settings, .. } = self;

fn view_settings(&self, ctx: &Context<Self>) -> Html {
// This helper macro creates a callback which applies the new value to the current settings and sends `Msg::ChangeSettings`.
// Thanks to this, we don't need to have "ChangeBoids", "ChangeCohesion", etc. messages,
// but it comes at the cost of cloning the `Settings` struct each time.
macro_rules! settings_callback {
($link:expr, $settings:ident; $key:ident as $ty:ty) => {{
let settings = $settings.clone();
$link.callback(move |value| {
($key:ident as $ty:ty) => {{
let settings = self.settings.clone();
ctx.callback(move |value| {
let mut settings = settings.clone();
settings.$key = value as $ty;
Msg::ChangeSettings(settings)
})
}};
($link:expr, $settings:ident; $key:ident) => {
settings_callback!($link, $settings; $key as f64)
}
($key:ident) => {
settings_callback!($key as f64)
};
}

let settings = &self.settings;
html! {
<div class="settings">
<Slider label="Number of Boids"
min=1.0 max=600.0
onchange=settings_callback!(link, settings; boids as usize)
value=settings.boids as f64
onchange=settings_callback!(amount_of_boids as usize)
value=settings.amount_of_boids as f64
/>
<Slider label="View Distance"
max=500.0 step=10.0
onchange=settings_callback!(link, settings; visible_range)
onchange=settings_callback!(visible_range)
value=settings.visible_range
/>
<Slider label="Spacing"
max=100.0
onchange=settings_callback!(link, settings; min_distance)
onchange=settings_callback!(min_distance)
value=settings.min_distance
/>
<Slider label="Max Speed"
max=50.0
onchange=settings_callback!(link, settings; max_speed)
onchange=settings_callback!(max_speed)
value=settings.max_speed
/>
<Slider label="Cohesion"
max=0.5 percentage=true
onchange=settings_callback!(link, settings; cohesion_factor)
onchange=settings_callback!(cohesion_factor)
value=settings.cohesion_factor
/>
<Slider label="Separation"
max=1.0 percentage=true
onchange=settings_callback!(link, settings; separation_factor)
onchange=settings_callback!(separation_factor)
value=settings.separation_factor
/>
<Slider label="Alignment"
max=0.5 percentage=true
onchange=settings_callback!(link, settings; alignment_factor)
onchange=settings_callback!(alignment_factor)
value=settings.alignment_factor
/>
<Slider label="Turn Speed"
max=1.5 percentage=true
onchange=settings_callback!(link, settings; turn_speed_ratio)
onchange=settings_callback!(turn_speed_ratio)
value=settings.turn_speed_ratio
/>
<Slider label="Color Adaption"
max=1.5 percentage=true
onchange=settings_callback!(link, settings; color_adapt_factor)
onchange=settings_callback!(color_adapt_factor)
value=settings.color_adapt_factor
/>
</div>
Expand Down
4 changes: 2 additions & 2 deletions examples/boids/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use yew::services::storage::{Area, StorageService};
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Settings {
/// amount of boids
pub boids: usize,
pub amount_of_boids: usize,
// time between each simulation tick
pub tick_interval_ms: u64,
/// view distance of a boid
Expand Down Expand Up @@ -57,7 +57,7 @@ impl Settings {
impl Default for Settings {
fn default() -> Self {
Self {
boids: 300,
amount_of_boids: 300,
tick_interval_ms: 50,
visible_range: 80.0,
min_distance: 15.0,
Expand Down
Loading