2月19日 16:22

What Performance Optimization Techniques Are Available in Yew and How to Improve Application Performance?

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

rust
pub 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

rust
use 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

rust
use 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

rust
use 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

rust
use 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

  1. Avoid unnecessary rendering: Use should_render, use_memo, use_callback
  2. Optimize list rendering: Use key attribute, virtual scrolling
  3. Reduce state updates: Only update state when necessary
  4. Optimize Wasm bundle size: Use appropriate compilation options
  5. Reduce serialization overhead: Direct memory manipulation instead of serialization
  6. Batch process requests: Combine multiple requests into batch requests
  7. Use ref to avoid rendering: Use use_ref for data that doesn't need to trigger rendering
  8. Lazy loading: Only load and render components when needed
  9. Monitor performance: Use Performance API to monitor application performance
  10. Code splitting: Split code into smaller chunks to improve load speed
标签:Yew