5月31日 01:21

Yew Hooks 怎么用,use_state 和 use_effect 该如何取舍?

Yew 的 Hooks 不是把 React API 换成 Rust 语法那么简单。它解决的是函数组件里状态、生命周期和副作用怎么组织的问题,但同时也把 Rust 的所有权、闭包捕获和依赖比较带进了前端开发。写得顺时,组件会很短;写得随意时,最常见的坑是闭包拿到旧值、依赖写错导致重复请求,或者把本该抽出去的业务状态塞进一个组件里。

常用 Hook 该怎么理解?

use_state 适合保存简单值,比如输入框文本、开关状态、当前页码。它返回的 UseStateHandle<T> 可以 clone 到事件闭包里,读取时用 *handle 解引用,更新时用 set。如果下一个值依赖旧值,简单计数器可以直接写,但复杂逻辑最好别把计算散落在多个回调中。

rust
use yew::prelude::*; #[function_component(Counter)] fn counter() -> Html { let count = use_state(|| 0); let onclick = { let count = count.clone(); Callback::from(move |_| count.set(*count + 1)) }; html! { <button {onclick}>{ format!("count = {}", *count) }</button> } }

use_effect_with 更适合处理副作用,比如订阅事件、请求接口、同步浏览器标题。它的关键不是“能不能执行”,而是依赖值什么时候变化。依赖放得太宽,会让请求频繁触发;依赖放得太窄,又会读到过期参数。需要清理资源时,返回的析构闭包一定要写,否则定时器、监听器和 WebSocket 这类资源会悄悄累积。

rust
#[function_component(PageTitle)] fn page_title() -> Html { let title = use_state(|| "Yew".to_string()); { let title = title.clone(); use_effect_with((*title).clone(), move |title| { gloo::utils::document().set_title(title); || () }); } html! { <input value={(*title).clone()} /> } }

use_reducer 适合状态字段多、更新规则明确的场景,例如表单编辑、购物车、步骤流。它把“状态如何变化”集中到 reducer 里,代码会比多个 use_state 稍重,但调试时更容易定位。边界是:如果只是一个布尔值或一个字符串,强行上 reducer 会让组件显得笨重。

自定义 Hook 什么时候值得写?

当同一段状态和副作用逻辑在两个以上组件里重复出现,就可以考虑自定义 Hook。比如本地存储同步、分页请求、窗口尺寸监听,都适合封装成 use_xxx。但自定义 Hook 不是工具函数,它内部也要遵守 Hook 调用顺序,不能放在条件分支里调用。

rust
#[hook] fn use_toggle(default: bool) -> (UseStateHandle<bool>, Callback<MouseEvent>) { let value = use_state(|| default); let toggle = { let value = value.clone(); Callback::from(move |_| value.set(!*value)) }; (value, toggle) }

追问

use_state 和 use_reducer 该怎么取舍?

use_state 的优势是轻,适合值少、更新简单的组件状态。use_reducer 的优势是把状态变化变成显式动作,适合多个字段互相影响的场景。踩坑点是很多人一开始全用 use_state,后来表单校验、异步加载和错误状态混在一起,回调里到处都是重复逻辑。经验上,只要你开始给同一个状态写三四个不同更新分支,就该考虑 reducer。

use_effect_with 的依赖为什么容易写错?

依赖值决定副作用什么时候重新执行,这和 Rust 闭包捕获一起看才准确。把整个结构体作为依赖,可能因为字段变化太多导致重复请求;只放一个 id,又可能漏掉筛选条件。边界是副作用内部用到但不希望触发重跑的值,通常要重新设计数据流,而不是随手 clone。最安全的做法是让依赖尽量小,并在代码审查时逐项对照副作用里真正使用的变量。

Yew Hook 可以放在 if 语句里吗?

不建议,也通常不该这么写。Hook 依赖固定调用顺序保存内部状态,条件调用会让下一次渲染时顺序变化,状态就可能对应错位置。需要条件行为时,应当始终调用 Hook,把条件放进 Hook 的回调或渲染内容里。这个规则看起来像 React,但在 Yew 里还叠加了 Rust 类型约束,错误信息有时不如业务意图直观。

闭包里 clone handle 会不会有性能问题?

多数场景不用担心,UseStateHandle 这类句柄 clone 的成本很低,它不是复制完整状态。真正要注意的是你在闭包里 clone 了大型业务对象,或者为了绕过所有权把整棵数据复制来复制去。取舍上,UI 回调里 clone 句柄是正常写法,clone 大对象则应考虑 Rc、reducer 或把数据拆小。踩坑最多的是异步任务里捕获了旧 handle,以为 set 后马上能读到新值。

自定义 Hook 会不会让代码更难读?

会,尤其是名字抽象、返回值过多的时候。自定义 Hook 的边界应该是“一段可复用的状态行为”,而不是把组件里的几行代码随手搬出去。好的 Hook 调用处能看出业务意图,比如 use_window_sizeuse_local_storage_state;差的 Hook 只会让读者在多个文件之间跳来跳去。团队里最好约定返回结构和命名方式,否则 Hooks 多了以后会比 class 组件还难查。

小结

Yew Hooks 的核心取舍是:简单状态用 use_state,规则复杂用 use_reducer,外部世界交互放进 use_effect_with,重复的状态行为再抽自定义 Hook。真正影响稳定性的不是 Hook 名字记没记住,而是依赖、清理和闭包捕获有没有想清楚。写 Yew 时多花一点时间整理状态边界,后面排查渲染和异步问题会省很多力。

标签:Yew