乐闻世界logo
搜索文章和话题

Yew 中如何使用 Hooks,有哪些常用的 Hook 函数?

2月19日 16:24

Yew 中的 Hooks 使用详解

Yew 从 0.19 版本开始支持 Hooks API,提供了类似 React Hooks 的功能,使组件编写更加简洁和函数式。

基础 Hooks

1. use_state

use_state 用于在函数组件中管理状态,类似于 React 的 useState

rust
#[function_component(Counter)] fn counter() -> Html { let counter = use_state(|| 0); let increment = { let counter = counter.clone(); Callback::from(move |_| counter.set(*counter + 1)) }; let decrement = { let counter = counter.clone(); Callback::from(move |_| counter.set(*counter - 1)) }; html! { <div> <button onclick={decrement}>{ "-" }</button> <span>{ *counter }</span> <button onclick={increment}>{ "+" }</button> </div> } }

使用技巧:

  • 初始值通过闭包提供,只在首次渲染时计算
  • 返回的 UseStateHandle 可以克隆以在闭包中使用
  • 使用 set() 方法更新状态
  • 使用 * 解引用获取当前值

2. use_reducer

use_reducer 用于复杂的状态逻辑,类似于 React 的 useReducer

rust
#[derive(Clone, PartialEq)] pub enum CounterAction { Increment, Decrement, IncrementBy(i32), Reset, } fn counter_reducer(state: i32, action: CounterAction) -> i32 { match action { CounterAction::Increment => state + 1, CounterAction::Decrement => state - 1, CounterAction::IncrementBy(amount) => state + amount, CounterAction::Reset => 0, } } #[function_component(ReducerCounter)] fn reducer_counter() -> Html { let (counter, dispatch) = use_reducer(counter_reducer, 0); let increment = dispatch.reform(|_| CounterAction::Increment); let decrement = dispatch.reform(|_| CounterAction::Decrement); let increment_by_5 = dispatch.reform(|_| CounterAction::IncrementBy(5)); let reset = dispatch.reform(|_| CounterAction::Reset); html! { <div> <button onclick={decrement}>{ "-" }</button> <span>{ *counter }</span> <button onclick={increment}>{ "+" }</button> <button onclick={increment_by_5}>{ "+5" }</button> <button onclick={reset}>{ "Reset" }</button> </div> } }

3. use_effect

use_effect 用于处理副作用,类似于 React 的 useEffect

rust
#[function_component(EffectExample)] fn effect_example() -> Html { let count = use_state(|| 0); // 基本效果 use_effect(move || { web_sys::console::log_1(&"Component mounted".into()); // 清理函数 || { web_sys::console::log_1(&"Component unmounted".into()); } }); // 带依赖的效果 let count_effect = { let count = count.clone(); use_effect(move || { web_sys::console::log_2( &"Count changed:".into(), &(*count).into() ); || {} }) }; let onclick = { let count = count.clone(); Callback::from(move |_| count.set(*count + 1)) }; html! { <div> <button onclick={onclick}>{ "Increment" }</button> <p>{ *count }</p> </div> } }

4. use_ref

use_ref 用于存储可变值,类似于 React 的 useRef

rust
#[function_component(RefExample)] fn ref_example() -> Html { let input_ref = use_node_ref(); let count_ref = use_ref(|| 0); let onfocus = Callback::from(|_| { web_sys::console::log_1(&"Input focused".into()); }); let onclick = Callback::from({ let input_ref = input_ref.clone(); let count_ref = count_ref.clone(); move |_| { if let Some(input) = input_ref.cast::<HtmlInputElement>() { web_sys::console::log_2( &"Input value:".into(), &input.value().into() ); } *count_ref.borrow_mut() += 1; web_sys::console::log_2( &"Click count:".into(), &(*count_ref.borrow()).into() ); } }); html! { <div> <input ref={input_ref} type="text" placeholder="Focus me" onfocus={onfocus} /> <button onclick={onclick}>{ "Log Value" }</button> </div> } }

高级 Hooks

5. use_context

use_context 用于消费 Context,类似于 React 的 useContext

rust
#[derive(Clone, PartialEq, Default)] pub struct ThemeContext { pub is_dark: bool, } #[function_component(ThemeProvider)] fn theme_provider() -> Html { let is_dark = use_state(|| false); let context = ThemeContext { is_dark: *is_dark, }; html! { <ContextProvider<ThemeContext> context={context}> <ThemedComponent /> </ContextProvider<ThemeContext>> } } #[function_component(ThemedComponent)] fn themed_component() -> Html { let theme = use_context::<ThemeContext>().unwrap(); let class = if theme.is_dark { "dark-theme" } else { "light-theme" }; html! { <div class={class}> { "Themed content" } </div> } }

6. use_memo

use_memo 用于记忆化计算结果,类似于 React 的 useMemo

rust
#[function_component(MemoExample)] fn memo_example() -> Html { let count = use_state(|| 0); let other = use_state(|| 0); // 记忆化计算 let expensive_value = use_memo( (*count, *other), |(count, other)| { web_sys::console::log_1(&"Computing expensive value".into()); count * 2 + other * 3 } ); let increment_count = { let count = count.clone(); Callback::from(move |_| count.set(*count + 1)) }; let increment_other = { let other = other.clone(); Callback::from(move |_| other.set(*other + 1)) }; html! { <div> <button onclick={increment_count}>{ "Count: " }{ *count }</button> <button onclick={increment_other}>{ "Other: " }{ *other }</button> <p>{ "Expensive value: " }{ **expensive_value }</p> </div> } }

7. use_callback

use_callback 用于记忆化回调函数,类似于 React 的 useCallback

rust
#[function_component(CallbackExample)] fn callback_example() -> Html { let count = use_state(|| 0); // 记忆化回调 let increment = use_callback(count.clone(), |count, _| { count.set(*count + 1); }); // 带参数的记忆化回调 let add_value = use_callback(count.clone(), |count, value: i32| { count.set(*count + value); }); html! { <div> <button onclick={increment}>{ "Increment" }</button> <button onclick={Callback::from(move |_| add_value.emit(5))}> { "Add 5" } </button> <p>{ *count }</p> </div> } }

自定义 Hooks

自定义 Hooks 是复用逻辑的强大方式。

rust
// 自定义 Hook:使用本地存储 pub fn use_local_storage<T>(key: &str, initial_value: T) -> UseStateHandle<T> where T: Clone + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static, { let state = use_state(|| { if let Ok(window) = web_sys::window() { if let Ok(Some(storage)) = window.local_storage() { if let Ok(Some(value)) = storage.get_item(key) { if let Ok(deserialized) = serde_json::from_str::<T>(&value) { return deserialized; } } } } initial_value }); let key = key.to_string(); let state_effect = state.clone(); use_effect(move || { if let Ok(window) = web_sys::window() { if let Ok(Some(storage)) = window.local_storage() { if let Ok(value) = serde_json::to_string(&*state_effect) { let _ = storage.set_item(&key, &value); } } } || {} }); state } // 使用自定义 Hook #[function_component(StorageExample)] fn storage_example() -> Html { let name = use_local_storage("username", String::new()); let oninput = { let name = name.clone(); Callback::from(move |e: InputEvent| { let input: HtmlInputElement = e.target_unchecked_into(); name.set(input.value()); }) }; html! { <div> <input type="text" value={(*name).clone()} oninput={oninput} placeholder="Enter your name" /> <p>{ "Your name: " }{ &*name }</p> </div> } }

Hooks 规则

使用 Hooks 时必须遵循以下规则:

  1. 只在函数组件或自定义 Hook 中调用 Hooks

    rust
    // ✅ 正确 #[function_component(MyComponent)] fn my_component() -> Html { let count = use_state(|| 0); html! { <div>{ *count }</div> } } // ❌ 错误 fn regular_function() { let count = use_state(|| 0); // 不能在普通函数中调用 }
  2. 只在顶层调用 Hooks

    rust
    // ✅ 正确 #[function_component(MyComponent)] fn my_component() -> Html { let count = use_state(|| 0); let name = use_state(|| String::new()); html! { <div>{ *count }</div> } } // ❌ 错误 #[function_component(MyComponent)] fn my_component() -> Html { let count = use_state(|| 0); if *count > 0 { let name = use_state(|| String::new()); // 不能在条件中调用 } html! { <div>{ *count }</div> } }
  3. 保持 Hooks 调用顺序一致

    rust
    // ✅ 正确:每次渲染都按相同顺序调用 #[function_component(MyComponent)] fn my_component() -> Html { let count = use_state(|| 0); let name = use_state(|| String::new()); html! { <div>{ *count }</div> } }

性能优化技巧

1. 使用 use_memo 避免重复计算

rust
let expensive_result = use_memo( (*input1, *input2), |(a, b)| { // 只在 input1 或 input2 变化时重新计算 complex_calculation(a, b) } );

2. 使用 use_callback 稳定回调引用

rust
let handle_click = use_callback( dependency.clone(), |dep, _| { // 只在 dependency 变化时创建新的回调 do_something(dep) } );

3. 合理使用 use_ref 避免不必要的状态更新

rust
let ref_count = use_ref(|| 0); // 不会触发重新渲染 *ref_count.borrow_mut() += 1;
标签:Yew