如何在Yew的use_effect_with_deps监听器中设置状态?
在Yew的use_effect_with_deps监听器中设置状态的解决方案
我在use_effect内添加了一个鼠标移动事件监听器,想要获取鼠标坐标并更新组件状态。使用use_node_ref绑定元素添加监听器时,出现了如下生命周期错误:
error[E0597]: `coordinates` does not live long enough --> src/test.rs:33:17 | 24 | |div_ref| { | --------- value captured here ... 29 | let listener = Closure::<dyn Fn(MouseEvent)>::wrap(Box::new(|event| { | ________________________________________________________________- 30 | | let x = event.x(); 31 | | let y = event.y(); 32 | | web_sys::console::log_1(&x.into()); 33 | | coordinates.set(Coordinates { x: x, y: y }); | | ^^^^^^^^^^^ borrowed value does not live long enough 34 | | 35 | | })); | |______________- cast requires that `coordinates` is borrowed for `'static` ... 53 | } | - `coordinates` dropped here while still borrowed
可复现示例代码:
use wasm_bindgen::prelude::Closure; use web_sys::HtmlElement; use yew::prelude::*; use wasm_bindgen::JsCast; use web_sys; use yew::{function_component, html, use_effect_with_deps, use_node_ref, Html}; struct Coordinates { x: i32, y: i32, } #[function_component] fn Test() -> Html { let div_ref = use_node_ref(); let coordinates: UseStateHandle<Coordinates> = use_state(|| Coordinates { x: 0, y: 0 }); { let div_ref = div_ref.clone(); let coordinates = coordinates.clone(); use_effect_with_deps( |div_ref| { let div = div_ref .cast::<HtmlElement>() .expect("div_ref not attached to div element"); let listener = Closure::<dyn Fn(MouseEvent)>::wrap(Box::new(|event| { let x = event.x(); let y = event.y(); web_sys::console::log_1(&x.into()); coordinates.set(Coordinates { x: x, y: y }); // <- here is the problem })); div.add_event_listener_with_callback( "mousemove", listener.as_ref().unchecked_ref(), ) .unwrap(); move || { div.remove_event_listener_with_callback( "mousemove", listener.as_ref().unchecked_ref(), ) .unwrap(); } }, div_ref, ); } html! { <div ref={div_ref}>"Test"</div> } } fn main() { yew::Renderer::<Test>::new().render(); }
问题原因
错误的核心是Closure::wrap要求闭包捕获的变量必须拥有'static生命周期,但UseStateHandle的克隆实例在use_effect_with_deps的作用域内无法自动满足这个要求——闭包需要长期存在(直到组件卸载或依赖更新),但原作用域的变量会在use_effect_with_deps执行完毕后被销毁,导致生命周期不匹配。
修复方案
通过move闭包转移UseStateHandle的所有权,同时确保闭包能安全满足'static要求,修改后的代码如下:
use wasm_bindgen::prelude::Closure; use web_sys::HtmlElement; use yew::prelude::*; use wasm_bindgen::JsCast; use web_sys; use yew::{function_component, html, use_effect_with_deps, use_node_ref, Html}; struct Coordinates { x: i32, y: i32, } #[function_component] fn Test() -> Html { let div_ref = use_node_ref(); let coordinates: UseStateHandle<Coordinates> = use_state(|| Coordinates { x: 0, y: 0 }); { let div_ref = div_ref.clone(); let coordinates = coordinates.clone(); use_effect_with_deps( |div_ref| { let div = div_ref .cast::<HtmlElement>() .expect("div_ref not attached to div element"); // 使用move闭包转移coordinates的所有权,满足'static要求 let listener = Closure::<dyn Fn(MouseEvent)>::wrap(Box::new(move |event| { let x = event.x(); let y = event.y(); web_sys::console::log_1(&x.into()); coordinates.set(Coordinates { x, y }); })); div.add_event_listener_with_callback( "mousemove", listener.as_ref().unchecked_ref(), ) .unwrap(); // 将listener移动到清理闭包中,确保移除监听器时能正确引用 move || { div.remove_event_listener_with_callback( "mousemove", listener.as_ref().unchecked_ref(), ) .unwrap(); } }, div_ref, ); } html! { <div ref={div_ref}>"Test"</div> } } fn main() { yew::Renderer::<Test>::new().render(); }
关键修改说明
- 使用
move闭包:在创建事件监听器的闭包时添加move关键字,将coordinates的所有权转移到闭包内部,这样闭包就能满足'static生命周期要求——UseStateHandle本身是基于Rc的智能指针,克隆和移动不会影响原实例的有效性。 - 正确处理
listener的生命周期:将listener移动到清理闭包中,确保在移除事件监听器时,闭包实例仍然存在,避免悬垂引用。
内容的提问来源于stack exchange,提问作者programandoconro
相关产品推荐
相关产品推荐

