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. 全局状态管理
对于复杂应用,可以使用全局状态管理库如 yewdux 或 yew-state。
使用 yewdux 示例
rustuse 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::fetch 或 reqwest 处理异步数据获取。
rustpub 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 | 中 |
最佳实践
- 从简单开始:优先使用组件内部状态,只在必要时升级到更复杂的方案
- 避免过度使用 Context:Context 适合全局状态,不适合频繁变化的局部状态
- 合理划分状态:将状态放在逻辑上最接近使用它的组件中
- 使用不可变数据:利用 Rust 的类型系统确保状态更新的安全性