5月31日 01:21

Yew 状态管理怎么选,use_state、reducer 和全局状态各适合什么场景?

Yew 状态管理最容易走两个极端:要么所有东西都塞进组件里的 use_state,要么一上来就找全局状态库。前者前期快,后期回调互相影响;后者结构看似专业,却可能让一个简单页面背上过重的抽象。更稳的思路是先看状态的“作用范围”:只影响一个组件、影响一组组件,还是影响整个应用。

本地状态:先从最小范围开始

组件内部状态适合输入框、弹窗开关、当前 tab、局部加载状态这类只在当前组件使用的值。函数组件里通常用 use_state,类组件里可以放在 struct 字段中。它的优点是直接、可读、改动小;边界是当多个兄弟组件都需要同一份数据时,继续往下传回调会很快变乱。

rust
use yew::prelude::*; #[function_component(FilterBox)] fn filter_box() -> Html { let keyword = use_state(String::new); let oninput = { let keyword = keyword.clone(); Callback::from(move |e: InputEvent| { let input: web_sys::HtmlInputElement = e.target_unchecked_into(); keyword.set(input.value()); }) }; html! { <input value={(*keyword).clone()} {oninput} /> } }

reducer:把复杂变化收拢到一个地方

当状态字段变多,或者同一个动作会同时影响多个字段,use_reducer 更合适。它让每次状态变化都通过 action 表达,适合表单向导、购物车、筛选条件、异步请求状态。缺点是样板代码更多,小组件用它会显得啰嗦。

rust
#[derive(Clone, PartialEq)] struct TodoState { items: Vec<String>, loading: bool, } enum TodoAction { Add(String), SetLoading(bool), } impl Reducible for TodoState { type Action = TodoAction; fn reduce(self: std::rc::Rc<Self>, action: Self::Action) -> std::rc::Rc<Self> { let mut next = (*self).clone(); match action { TodoAction::Add(text) => next.items.push(text), TodoAction::SetLoading(v) => next.loading = v, } next.into() } }

Context 和全局状态:别太早上

跨多层组件共享状态时,可以用 Context Provider,把状态放在上层,再由子组件读取。它适合主题、登录用户、语言、权限、全局配置这类应用级信息。踩坑点是把频繁变化的大对象放进全局 Context,导致很多组件跟着重渲染。全局状态不是仓库,越往里面塞,边界越模糊。

rust
type AppState = UseReducerHandle<UserState>; #[function_component(App)] fn app() -> Html { let state = use_reducer(|| UserState { name: "Guest".into(), logged_in: false, }); html! { <ContextProvider<AppState> context={state}> <MainPage /> </ContextProvider<AppState>> } }

如果状态涉及服务端缓存、分页列表、乐观更新和失败重试,单靠组件状态会很吃力。可以自己封装请求 Hook,也可以引入社区状态方案,但要先确认维护状态和项目兼容性。Yew 生态还不如 React 丰富,选库时要看版本活跃度、文档和与当前 Yew 版本的匹配情况。

追问

什么时候不用全局状态?

只被一个组件或一小段组件树使用的状态,不该放全局。比如弹窗开关、临时输入、局部排序方式,放近一点更容易理解。全局状态的成本是隐式依赖变多,任何组件都可能读写它。项目里常见的坑是“为了方便”把所有状态放进 Context,最后没人知道某个字段在哪里被改了。

use_state 多了就一定要换 reducer 吗?

不一定,数量不是唯一标准。真正的信号是多个状态必须一起更新,或者更新规则已经开始重复。比如 loadingdataerror 三个字段围绕一次请求变化,用 reducer 会更清晰。反过来,三个互不相关的小开关继续用 use_state 没问题,强行合并只会增加阅读成本。

Context 会不会导致性能问题?

会,但不是用了就慢,而是值变化太频繁、范围太大时才明显。Provider 的 context 值变化后,消费者组件会重新响应,若里面挂了大列表或复杂子树,体验就可能受影响。取舍上,可以把低频状态和高频状态拆成不同 Context,或者把频繁变化的局部状态留在组件内。性能问题最好用实际 profiling 看,不要凭感觉过早优化。

异步请求状态该放哪里?

如果请求只服务当前组件,放本地 Hook 就够了,比如 loading/data/error 一起维护。多个页面共享同一份服务端数据时,可以考虑上提到 Context 或封装缓存层。边界是服务端状态和客户端 UI 状态不要混在一起,前者有过期、刷新、失败重试,后者更多是界面临时行为。很多 bug 来自把接口返回当成永远正确的全局真相,结果切换用户或参数后仍显示旧数据。

状态管理库值得引入吗?

值得与否取决于项目复杂度,而不是技术偏好。小项目引库会增加学习和升级成本,大项目不用库又可能让状态流散在各处。选库时要看它是否支持当前 Yew 版本、是否有活跃维护、是否方便测试。最怕的是为了“架构完整”引入库,却没有统一使用规范,最后同时存在本地状态、Context 和第三方 store 三套写法。

小结

Yew 状态管理可以按范围逐级选择:组件内用 use_state,复杂局部逻辑用 use_reducer,跨层共享再用 Context,全应用级状态和服务端缓存再考虑专门方案。不要太早抽象,也不要等到回调和 props 传递失控才整理边界。状态放在哪里,决定了后面调试问题时要翻多少文件。

标签:Yew