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

How to Manage State in Yew and What Are the Different State Management Solutions Available?

2月19日 16:23

State Management Solutions in Yew

Yew provides multiple state management solutions, from simple component-level state to complex application-level state management.

1. Component Internal State

Component internal state is the simplest state management approach, suitable for local state within a single component.

rust
#[derive(PartialEq, Properties)] pub struct Props { #[prop_or_default] pub initial_value: i32, } pub struct Counter { count: i32, } pub enum Msg { Increment, Decrement, } impl Component for Counter { type Message = Msg; type Properties = Props; fn create(ctx: &Context<Self>) -> Self { Self { count: ctx.props().initial_value, } } fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool { match msg { Msg::Increment => { self.count += 1; true } Msg::Decrement => { self.count -= 1; true } } } fn view(&self, ctx: &Context<Self>) -> Html { html! { <div> <button onclick={ctx.link().callback(|_| Msg::Decrement)}>{ "-" }</button> <span>{ self.count }</span> <button onclick={ctx.link().callback(|_| Msg::Increment)}>{ "+" }</button> </div> } } }

2. Props (Property Passing)

Props are used to pass data from parent components to child components, part of the unidirectional data flow.

rust
#[derive(PartialEq, Properties)] pub struct ChildProps { pub value: String, #[prop_or_default] pub on_change: Callback<String>, } pub struct ChildComponent; impl Component for ChildComponent { type Message = (); type Properties = ChildProps; fn view(&self, ctx: &Context<Self>) -> Html { html! { <div> <input value={ctx.props().value.clone()} onchange={ctx.props().on_change.reform(|e: Event| { let input: HtmlInputElement = e.target_unchecked_into(); input.value() })} /> </div> } } }

3. Context API

The Context API allows sharing state across the component tree, avoiding props drilling.

rust
// Define Context #[derive(Clone, PartialEq)] pub struct ThemeContext { pub is_dark: bool, pub toggle: Callback<()>, } // Provider Component pub struct ThemeProvider { is_dark: bool, } impl Component for ThemeProvider { type Message = Msg; type Properties = (); fn create(_ctx: &Context<Self>) -> Self { Self { is_dark: false } } fn view(&self, ctx: &Context<Self>) -> Html { let context = ThemeContext { is_dark: self.is_dark, toggle: ctx.link().callback(|_| Msg::ToggleTheme), }; html! { <ContextProvider<ThemeContext> context={context}> { ctx.props().children.clone() } </ContextProvider<ThemeContext>> } } } // Consumer Component pub struct ThemedComponent; impl Component for ThemedComponent { type Message = (); type Properties = (); fn view(&self, ctx: &Context<Self>) -> Html { let theme = ctx.link().context::<ThemeContext>(|ctx| { (ctx.clone(), ()) }); if let Some(theme) = theme { let class = if theme.is_dark { "dark-theme" } else { "light-theme" }; html! { <div class={class}> { "Themed Content" } </div> } } else { html! { <div>{ "No theme context" }</div> } } } }

4. Global State Management

For complex applications, you can use global state management libraries like yewdux or yew-state.

Using yewdux Example

rust
use yewdux::prelude::*; // Define Store #[derive(Clone, PartialEq, Store)] pub struct AppState { pub user: Option<User>, pub notifications: Vec<Notification>, } impl Default for AppState { fn default() -> Self { Self { user: None, notifications: Vec::new(), } } } // Use in Component pub struct UserComponent; impl Component for UserComponent { type Message = (); type Properties = (); fn view(&self, ctx: &Context<Self>) -> Html { let (state, dispatch) = use_store::<AppState>(); html! { <div> { state.user.as_ref().map(|user| { html! { <p>{ format!("Hello, {}!", user.name) }</p> } })} <button onclick={dispatch.reduce_callback(|state| { state.user = Some(User { name: "John Doe".to_string(), }); })}> { "Login" } </button> </div> } } }

5. Async State Management

Use yew::services::fetch or reqwest to handle asynchronous data fetching.

rust
pub struct AsyncData { data: Option<String>, loading: bool, error: Option<String>, } pub enum Msg { FetchData, DataReceived(String), FetchError(String), } impl Component for AsyncData { type Message = Msg; type Properties = (); fn create(_ctx: &Context<Self>) -> Self { Self { data: None, loading: false, error: None, } } fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool { match msg { Msg::FetchData => { self.loading = true; self.error = None; // Simulate async request ctx.link().send_future(async { // This could be an actual API call tokio::time::sleep(Duration::from_secs(1)).await; Msg::DataReceived("Fetched data".to_string()) }); true } Msg::DataReceived(data) => { self.data = Some(data); self.loading = false; true } Msg::FetchError(error) => { self.error = Some(error); self.loading = false; true } } } fn view(&self, ctx: &Context<Self>) -> Html { html! { <div> <button onclick={ctx.link().callback(|_| Msg::FetchData)}> { "Fetch Data" } </button> { if self.loading { html! { <p>{ "Loading..." }</p> } } else if let Some(ref error) = self.error { html! { <p class="error">{ error }</p> } } else if let Some(ref data) = self.data { html! { <p>{ data }</p> } } else { html! { <p>{ "No data" }</p> } }} </div> } } }

State Management Selection Guide

ScenarioRecommended SolutionComplexity
Single component local stateComponent internal stateLow
Parent-child communicationProps + CallbackLow
Cross-level component sharingContext APIMedium
Complex application stateyewdux / yew-stateHigh
Async data handlingFuture + ServicesMedium

Best Practices

  1. Start simple: Prioritize component internal state, upgrade to more complex solutions only when necessary
  2. Avoid overusing Context: Context is suitable for global state, not for frequently changing local state
  3. Reasonably partition state: Place state in the component logically closest to where it's used
  4. Use immutable data: Leverage Rust's type system to ensure state update safety
标签:Yew