Yew 性能优化技巧
Yew 应用虽然基于 WebAssembly 具有天然的性能优势,但通过合理的优化策略可以进一步提升应用性能。
组件渲染优化
1. 使用 should_render 控制渲染
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 => { // 状态变化但不需要重新渲染 self.value += 1; false } } } fn should_render(&self, ctx: &Context<Self>) -> bool { // 只在值真正变化时才渲染 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. 使用 use_memo 记忆化计算
rust#[function_component(MemoizedCalculation)] fn memoized_calculation() -> Html { let input1 = use_state(|| 0); let input2 = use_state(|| 0); // 只在依赖变化时重新计算 let expensive_result = use_memo( (*input1, *input2), |(a, b)| { web_sys::console::log_1(&"Computing expensive result".into()); // 模拟复杂计算 (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. 使用 use_callback 稳定回调引用
rust#[function_component(CallbackOptimization)] fn callback_optimization() -> Html { let count = use_state(|| 0); let expensive_operation = use_state(|| 0); // 稳定的回调,只在依赖变化时重新创建 let handle_click = use_callback( count.clone(), |count, _| { count.set(*count + 1); } ); // 另一个稳定的回调 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> } }
列表渲染优化
1. 使用 key 属性
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. 虚拟滚动
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; // 每个项目的像素高度 let container_height = 1000; // 容器高度 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> } }
状态管理优化
1. 减少不必要的状态更新
rust#[function_component(OptimizedState)] fn optimized_state() -> Html { let counter = use_state(|| 0); let display_value = use_state(|| 0); // 只在真正需要时更新显示值 let increment = { let counter = counter.clone(); let display_value = display_value.clone(); Callback::from(move |_| { counter.set(*counter + 1); // 只在特定条件下更新显示 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. 使用 use_reducer 优化复杂状态
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 优化
1. 优化 Wasm 包大小
toml# Cargo.toml [profile.release] opt-level = "z" # 优化大小 lto = true # 链接时优化 codegen-units = 1 # 更好的优化 panic = "abort" # 减少恐慌处理代码 strip = true # 移除符号 [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. 减少序列化开销
rustuse wasm_bindgen::prelude::*; use js_sys::Uint32Array; // 不好的做法:频繁序列化 #[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() } // 好的做法:直接操作内存 #[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); } }
网络请求优化
1. 请求去重
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| { // 检查是否已有相同请求在进行 if pending_requests.borrow().contains(&url) { return; } pending_requests.borrow_mut().insert(url.clone()); loading.set(true); spawn_local(async move { // 模拟 API 调用 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. 批量请求
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 { // 批量请求而不是逐个请求 let batch_data = (*items).clone(); // 模拟批量 API 调用 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> } }
内存优化
1. 使用 use_ref 避免不必要的状态更新
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); // 使用 ref 不会触发重新渲染 *click_count.borrow_mut() += 1; // 只在需要时记录 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. 延迟加载组件
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 { // 这个组件只在需要时才渲染 html! { <div> <h1>{ "Heavy Component" }</h1> <p>{ "This component is only rendered when needed" }</p> </div> } }
性能监控
1. 使用 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(); // 模拟渲染操作 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> } }
最佳实践总结
- 避免不必要的渲染:使用
should_render、use_memo、use_callback - 优化列表渲染:使用
key属性、虚拟滚动 - 减少状态更新:只在必要时更新状态
- 优化 Wasm 包大小:使用适当的编译选项
- 减少序列化开销:直接操作内存而非序列化
- 批量处理请求:合并多个请求为批量请求
- 使用 ref 避免渲染:对于不需要触发渲染的数据使用
use_ref - 延迟加载:只在需要时加载和渲染组件
- 监控性能:使用 Performance API 监控应用性能
- 代码分割:将代码分割成更小的块以提高加载速度