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

Yew 中如何进行状态管理,有哪些不同的状态管理方案?

2月19日 16:23

Yew 中的状态管理方案

Yew 提供了多种状态管理方案,从简单的组件内部状态到复杂的应用级状态管理。

1. 组件内部状态

组件内部状态是最简单的状态管理方式,适用于单个组件的本地状态。

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(属性传递)

Props 用于从父组件向子组件传递数据,是单向数据流的一部分。

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

Context API 允许在组件树中共享状态,避免 props drilling。

rust
// 定义 Context #[derive(Clone, PartialEq)] pub struct ThemeContext { pub is_dark: bool, pub toggle: Callback<()>, } // 提供者组件 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>> } } } // 消费者组件 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. 全局状态管理

对于复杂应用,可以使用全局状态管理库如 yewduxyew-state

使用 yewdux 示例

rust
use yewdux::prelude::*; // 定义 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(), } } } // 在组件中使用 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. 异步状态管理

使用 yew::services::fetchreqwest 处理异步数据获取。

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; // 模拟异步请求 ctx.link().send_future(async { // 这里可以是实际的 API 调用 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> } } }

状态管理选择指南

场景推荐方案复杂度
单个组件本地状态组件内部状态
父子组件通信Props + Callback
跨层级组件共享Context API
复杂应用状态yewdux / yew-state
异步数据处理Future + Services

最佳实践

  1. 从简单开始:优先使用组件内部状态,只在必要时升级到更复杂的方案
  2. 避免过度使用 Context:Context 适合全局状态,不适合频繁变化的局部状态
  3. 合理划分状态:将状态放在逻辑上最接近使用它的组件中
  4. 使用不可变数据:利用 Rust 的类型系统确保状态更新的安全性
标签:Yew