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