Yew Performance Optimization Techniques
While Yew applications have natural performance advantages due to WebAssembly, reasonable optimization strategies can further improve application performance.
Component Rendering Optimization
1. Using should_render to Control Rendering
rustpub struct OptimizedComponent { value: i32, last_rendered_value: i32, } impl Component for OptimizedComponent { type Message = Msg; type Properties = Props; fn create(ctx: &Context<Self>) -> Self { Self { value: 0, last_rendered_value: 0, } } fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool { match msg { Msg::Increment => { self.value += 1; true } Msg::NoChange => { // State changes but no need to re-render self.value += 1; false } } } fn should_render(&self, ctx: &Context<Self>) -> bool { // Only render when value actually changes self.value != self.last_rendered_value } fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) { self.last_rendered_value = self.value; } fn view(&self, ctx: &Context<Self>) -> Html { html! { <div> <p>{ "Value: " }{ self.value }</p> </div> } } }
2. Using use_memo for Memoized Calculations
rust#[function_component(MemoizedCalculation)] fn memoized_calculation() -> Html { let input1 = use_state(|| 0); let input2 = use_state(|| 0); // Only recalculate when dependencies change let expensive_result = use_memo( (*input1, *input2), |(a, b)| { web_sys::console::log_1(&"Computing expensive result".into()); // Simulate complex calculation (0..1000000).fold(0i64, |acc, _| acc + 1) as i32 + a + b } ); html! { <div> <button onclick={Callback::from({ let input1 = input1.clone(); move |_| input1.set(*input1 + 1) })}> { "Increment Input 1" } </button> <button onclick={Callback::from({ let input2 = input2.clone(); move |_| input2.set(*input2 + 1) })}> { "Increment Input 2" } </button> <p>{ "Result: " }{ **expensive_result }</p> </div> } }
3. Using use_callback to Stabilize Callback References
rust#[function_component(CallbackOptimization)] fn callback_optimization() -> Html { let count = use_state(|| 0); let expensive_operation = use_state(|| 0); // Stable callback, only recreated when dependencies change let handle_click = use_callback( count.clone(), |count, _| { count.set(*count + 1); } ); // Another stable callback let handle_expensive = use_callback( expensive_operation.clone(), |expensive_op, value: i32| { expensive_op.set(*expensive_op + value); } ); html! { <div> <ChildComponent on_click={handle_click.clone()} /> <button onclick={Callback::from(move |_| handle_expensive.emit(10))}> { "Expensive Operation" } </button> </div> } } #[derive(Properties, PartialEq)] pub struct ChildProps { pub on_click: Callback<()>, } #[function_component(ChildComponent)] fn child_component(props: &ChildProps) -> Html { html! { <button onclick={props.on_click.clone()}> { "Click Me" } </button> } }
List Rendering Optimization
1. Using key Attribute
rust#[function_component(OptimizedList)] fn optimized_list() -> Html { let items = use_state(|| vec![ Item { id: 1, text: "Item 1".to_string() }, Item { id: 2, text: "Item 2".to_string() }, Item { id: 3, text: "Item 3".to_string() }, ]); let add_item = { let items = items.clone(); Callback::from(move |_| { let mut new_items = (*items).clone(); new_items.push(Item { id: new_items.len() as u32 + 1, text: format!("Item {}", new_items.len() + 1), }); items.set(new_items); }) }; html! { <div> <button onclick={add_item}>{ "Add Item" }</button> <ul> { items.iter().map(|item| { html! { <li key={item.id}> { &item.text } </li> } }).collect::<Html>() } </ul> </div> } } #[derive(Clone, PartialEq)] struct Item { id: u32, text: String, }
2. Virtual Scrolling
rustuse yew::prelude::*; #[function_component(VirtualScroll)] fn virtual_scroll() -> Html { let items = use_state(|| (0..1000).collect::<Vec<i32>>()); let visible_range = use_state(|| (0, 20)); // (start, end) let item_height = 50; // Pixel height of each item let container_height = 1000; // Container height let onscroll = { let visible_range = visible_range.clone(); Callback::from(move |e: Event| { let element: HtmlElement = e.target_unchecked_into(); let scroll_top = element.scroll_top() as i32; let start = (scroll_top / item_height).max(0); let end = (start + (container_height / item_height) + 1).min(999); visible_range.set((start, end)); }) }; let (start, end) = *visible_range; let visible_items = &items[start..=end]; html! { <div style={format!("height: {}px; overflow-y: auto;", container_height)} onscroll={onscroll}> <div style={format!("height: {}px;", items.len() * item_height)}> <div style={format!("position: absolute; top: {}px;", start * item_height)}> { visible_items.iter().map(|item| { html! { <div key={*item} style={format!("height: {}px;", item_height)}> { format!("Item {}", item) } </div> } }).collect::<Html>() } </div> </div> </div> } }
State Management Optimization
1. Reduce Unnecessary State Updates
rust#[function_component(OptimizedState)] fn optimized_state() -> Html { let counter = use_state(|| 0); let display_value = use_state(|| 0); // Only update display value when really needed let increment = { let counter = counter.clone(); let display_value = display_value.clone(); Callback::from(move |_| { counter.set(*counter + 1); // Only update display under specific conditions if *counter % 10 == 0 { display_value.set(*counter); } }) }; html! { <div> <button onclick={increment}>{ "Increment" }</button> <p>{ "Counter: " }{ *counter }</p> <p>{ "Display: " }{ *display_value }</p> </div> } }
2. Using use_reducer to Optimize Complex State
rust#[derive(Clone, PartialEq)] pub enum AppStateAction { Increment, Decrement, SetValue(i32), Reset, } fn app_state_reducer(state: i32, action: AppStateAction) -> i32 { match action { AppStateAction::Increment => state + 1, AppStateAction::Decrement => state - 1, AppStateAction::SetValue(value) => value, AppStateAction::Reset => 0, } } #[function_component(ReducerOptimization)] fn reducer_optimization() -> Html { let (state, dispatch) = use_reducer(app_state_reducer, 0); html! { <div> <button onclick={dispatch.reform(|_| AppStateAction::Increment)}> { "+" } </button> <button onclick={dispatch.reform(|_| AppStateAction::Decrement)}> { "-" } </button> <button onclick={dispatch.reform(|_| AppStateAction::Reset)}> { "Reset" } </button> <p>{ "State: " }{ *state }</p> </div> } }
WebAssembly Optimization
1. Optimize Wasm Bundle Size
toml# Cargo.toml [profile.release] opt-level = "z" # Optimize for size lto = true # Link-time optimization codegen-units = 1 # Better optimization panic = "abort" # Reduce panic handling code strip = true # Remove symbols [dependencies] wee_alloc = { version = "0.4", optional = true } [features] default = ["wee_alloc"]
rust// lib.rs #[cfg(feature = "wee_alloc")] #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
2. Reduce Serialization Overhead
rustuse wasm_bindgen::prelude::*; use js_sys::Uint32Array; // Bad practice: frequent serialization #[wasm_bindgen] pub fn process_data_serialized(data: JsValue) -> JsValue { let parsed: Vec<i32> = serde_wasm_bindgen::from_value(data).unwrap(); let result: Vec<i32> = parsed.iter().map(|x| x * 2).collect(); serde_wasm_bindgen::to_value(&result).unwrap() } // Good practice: direct memory manipulation #[wasm_bindgen] pub fn process_data_in_place(array: &Uint32Array) { for i in 0..array.length() { let value = array.get(i); array.set(i, value * 2); } }
Network Request Optimization
1. Request Deduplication
rustuse std::collections::HashSet; use yew::prelude::*; #[function_component(RequestDeduplication)] fn request_deduplication() -> Html { let data = use_state(|| None::<String>); let loading = use_state(|| false); let pending_requests = use_mut_ref(|| HashSet::<String>::new()); let fetch_data = { let data = data.clone(); let loading = loading.clone(); let pending_requests = pending_requests.clone(); Callback::from(move |url: String| { // Check if same request is already in progress if pending_requests.borrow().contains(&url) { return; } pending_requests.borrow_mut().insert(url.clone()); loading.set(true); spawn_local(async move { // Simulate API call gloo_timers::future::sleep(std::time::Duration::from_secs(1)).await; data.set(Some(format!("Data from {}", url))); loading.set(false); pending_requests.borrow_mut().remove(&url); }); }) }; html! { <div> <button onclick={Callback::from(move |_| fetch_data.emit("api1".to_string()))}> { "Fetch API 1" } </button> <button onclick={Callback::from(move |_| fetch_data.emit("api2".to_string()))}> { "Fetch API 2" } </button> { if *loading { html! { <p>{ "Loading..." }</p> } } else if let Some(ref value) = *data { html! { <p>{ value }</p> } }} </div> } }
2. Batch Requests
rust#[function_component(BatchRequests)] fn batch_requests() -> Html { let items = use_state(|| vec!["item1", "item2", "item3"]); let results = use_state(|| Vec::<String>::new()); let loading = use_state(|| false); let fetch_batch = { let items = items.clone(); let results = results.clone(); let loading = loading.clone(); Callback::from(move |_| { loading.set(true); spawn_local(async move { // Batch request instead of individual requests let batch_data = (*items).clone(); // Simulate batch API call gloo_timers::future::sleep(std::time::Duration::from_secs(1)).await; let batch_results: Vec<String> = batch_data .iter() .map(|item| format!("Result for {}", item)) .collect(); results.set(batch_results); loading.set(false); }); }) }; html! { <div> <button onclick={fetch_batch} disabled={*loading}> { if *loading { "Loading..." } else { "Fetch Batch" } } </button> <ul> { results.iter().map(|result| { html! { <li>{ result }</li> } }).collect::<Html>() } </ul> </div> } }
Memory Optimization
1. Using use_ref to Avoid Unnecessary State Updates
rust#[function_component(RefOptimization)] fn ref_optimization() -> Html { let counter = use_state(|| 0); let click_count = use_ref(|| 0); let increment = { let counter = counter.clone(); let click_count = click_count.clone(); Callback::from(move |_| { counter.set(*counter + 1); // Using ref doesn't trigger re-render *click_count.borrow_mut() += 1; // Only log when needed if *click_count.borrow() % 10 == 0 { web_sys::console::log_2( &"Total clicks:".into(), &(*click_count.borrow()).into() ); } }) }; html! { <div> <button onclick={increment}>{ "Increment" }</button> <p>{ "Counter: " }{ *counter }</p> </div> } }
2. Lazy Loading Components
rust#[function_component(LazyLoad)] fn lazy_load() -> Html { let show_component = use_state(|| false); html! { <div> <button onclick={Callback::from({ let show_component = show_component.clone(); move |_| show_component.set(!*show_component) })}> { if *show_component { "Hide" } else { "Show" } } </button> { if *show_component { html! { <HeavyComponent /> } } else { html! {} }} </div> } } #[function_component(HeavyComponent)] fn heavy_component() -> Html { // This component is only rendered when needed html! { <div> <h1>{ "Heavy Component" }</h1> <p>{ "This component is only rendered when needed" }</p> </div> } }
Performance Monitoring
1. Using Performance API
rustuse web_sys::Performance; #[function_component(PerformanceMonitor)] fn performance_monitor() -> Html { let render_time = use_state(|| 0.0); let measure_render = { let render_time = render_time.clone(); Callback::from(move |_| { if let Ok(performance) = Performance::new() { let start = performance.now(); // Simulate render operation let _ = std::hint::black_box(42); let end = performance.now(); render_time.set(end - start); } }) }; html! { <div> <button onclick={measure_render}>{ "Measure Render" }</button> <p>{ "Render time: " }{ *render_time }{ " ms" }</p> </div> } }
Best Practices Summary
- Avoid unnecessary rendering: Use
should_render,use_memo,use_callback - Optimize list rendering: Use
keyattribute, virtual scrolling - Reduce state updates: Only update state when necessary
- Optimize Wasm bundle size: Use appropriate compilation options
- Reduce serialization overhead: Direct memory manipulation instead of serialization
- Batch process requests: Combine multiple requests into batch requests
- Use ref to avoid rendering: Use
use_reffor data that doesn't need to trigger rendering - Lazy loading: Only load and render components when needed
- Monitor performance: Use Performance API to monitor application performance
- Code splitting: Split code into smaller chunks to improve load speed