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

How to Use Hooks in Yew and What Are the Common Hook Functions Available?

2月19日 16:24

Using Hooks in Yew - Detailed Guide

Yew has supported Hooks API since version 0.19, providing functionality similar to React Hooks, making component writing more concise and functional.

Basic Hooks

1. use_state

use_state is used to manage state in function components, similar to React's 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> } }

Usage Tips:

  • Initial value is provided through a closure, only computed on first render
  • Returned UseStateHandle can be cloned for use in closures
  • Use set() method to update state
  • Use * to dereference and get current value

2. use_reducer

use_reducer is used for complex state logic, similar to React's 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 is used to handle side effects, similar to React's useEffect.

rust
#[function_component(EffectExample)] fn effect_example() -> Html { let count = use_state(|| 0); // Basic effect use_effect(move || { web_sys::console::log_1(&"Component mounted".into()); // Cleanup function || { web_sys::console::log_1(&"Component unmounted".into()); } }); // Effect with dependencies 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 is used to store mutable values, similar to React's 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> } }

Advanced Hooks

5. use_context

use_context is used to consume Context, similar to React's 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 is used to memoize computation results, similar to React's useMemo.

rust
#[function_component(MemoExample)] fn memo_example() -> Html { let count = use_state(|| 0); let other = use_state(|| 0); // Memoized computation 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 is used to memoize callback functions, similar to React's useCallback.

rust
#[function_component(CallbackExample)] fn callback_example() -> Html { let count = use_state(|| 0); // Memoized callback let increment = use_callback(count.clone(), |count, _| { count.set(*count + 1); }); // Memoized callback with parameters 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> } }

Custom Hooks

Custom Hooks are a powerful way to reuse logic.

rust
// Custom Hook: using local storage 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 } // Using custom 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 Rules

When using Hooks, you must follow these rules:

  1. Only call Hooks in function components or custom Hooks

    rust
    // ✅ Correct #[function_component(MyComponent)] fn my_component() -> Html { let count = use_state(|| 0); html! { <div>{ *count }</div> } } // ❌ Wrong fn regular_function() { let count = use_state(|| 0); // Cannot call in regular function }
  2. Only call Hooks at the top level

    rust
    // ✅ Correct #[function_component(MyComponent)] fn my_component() -> Html { let count = use_state(|| 0); let name = use_state(|| String::new()); html! { <div>{ *count }</div> } } // ❌ Wrong #[function_component(MyComponent)] fn my_component() -> Html { let count = use_state(|| 0); if *count > 0 { let name = use_state(|| String::new()); // Cannot call in condition } html! { <div>{ *count }</div> } }
  3. Keep Hooks call order consistent

    rust
    // ✅ Correct: called in same order on every render #[function_component(MyComponent)] fn my_component() -> Html { let count = use_state(|| 0); let name = use_state(|| String::new()); html! { <div>{ *count }</div> } }

Performance Optimization Tips

1. Use use_memo to avoid repeated calculations

rust
let expensive_result = use_memo( (*input1, *input2), |(a, b)| { // Only recalculate when input1 or input2 changes complex_calculation(a, b) } );

2. Use use_callback to stabilize callback references

rust
let handle_click = use_callback( dependency.clone(), |dep, _| { // Only create new callback when dependency changes do_something(dep) } );

3. Use use_ref appropriately to avoid unnecessary state updates

rust
let ref_count = use_ref(|| 0); // Won't trigger re-render *ref_count.borrow_mut() += 1;
标签:Yew