如何在Yew结构体组件中用wasm_bindgen_futures请求API并更新状态
Yew结构体组件异步API请求的生命周期问题
问题描述
我有一个Yew结构体组件,需要向API发送GET请求并渲染返回的条目列表。尝试在组件的render方法中执行请求时遇到了生命周期问题:无法在wasm_bindgen_future中使用self引用,因为self的匿名生命周期无法满足'static要求。必须使用wasm_bindgen_futures执行异步API请求,相关代码如下:
pub struct ViewLessonPlans { lesson_plans: Vec<LessonPlan>, loading_condition: ComponentLoadingStage } impl Component for ViewLessonPlans { type Message = (); type Properties = (); fn create(ctx: &Context<Self>) -> Self { Self { lesson_plans: vec![], loading_condition: ComponentLoadingStage::Loading } } fn view(&self, ctx: &Context<Self>) -> Html { match self.loading_condition { ComponentLoadingStage::Loading => { html! { <h1>{ "Lesson Plans Loading" }</h1>} }, ComponentLoadingStage::Success => { self.lesson_plans.iter().map(|lp| { html! { <ViewLessonPlan lesson_plan={lp.clone()} /> } }).collect::<Html>() }, ComponentLoadingStage::Error => { html! { <h1>{ "There was an error loading the lesson plans!" }</h1>} }, } } fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) { if first_render { wasm_bindgen_futures::spawn_local(async move { match get_lesson_plans().await { Ok(lesson_plans) => { self.lesson_plans = lesson_plans.iter().map(|(_id, lp)| { lp.clone() }).collect(); self.loading_condition = ComponentLoadingStage::Success; }, Err(_) => { self.loading_condition = ComponentLoadingStage::Error; }, } }); } } }
报错信息
`self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement ...is used here...rustcE0759
请问如何完成该API请求并使用响应更新self?
附功能可用的function_component版本,但刷新时会短暂显示错误状态,暂未明确原因:
#[function_component(ViewLessonPlans)] pub fn view_lesson_plans() -> Html { // Setup the state let state_init: UseStateHandle<ComponentLoadingStage<Vec<LessonPlan>>> = use_state(|| ComponentLoadingStage::Loading); let state = state_init.clone(); // Perform the API request wasm_bindgen_futures::spawn_local(async move { match get_lesson_plans().await { Ok(lesson_plans) => { state.set(ComponentLoadingStage::Success( lesson_plans.iter().map(|(_id, lp)| lp.clone()).collect(), )); } Err(_) => { state.set(ComponentLoadingStage::Error); } } }); // Return the view match (*state_init).clone() { ComponentLoadingStage::Loading => { html! { <h1>{ "Lesson Plans Loading" }</h1>} } ComponentLoadingStage::Success(lesson_plans) => lesson_plans .iter() .map(|lp| { html! { <ViewLessonPlan lesson_plan={lp.clone()} /> } }) .collect::<Html>(), ComponentLoadingStage::Error => { html! { <h1>{ "There was an error loading the lesson plans!" }</h1>} } } }
解决方案
结构体组件的正确实现
Yew结构体组件不能直接在spawn_local的异步闭包中修改self,因为闭包要求捕获的变量拥有'static生命周期,而&mut self的匿名生命周期无法满足。正确的做法是利用Yew的Message机制传递异步结果,在update方法中更新状态:
pub struct ViewLessonPlans { lesson_plans: Vec<LessonPlan>, loading_condition: ComponentLoadingStage, } // 定义消息类型,用于传递API请求结果 #[derive(Debug)] enum Msg { LoadSuccess(Vec<LessonPlan>), LoadError, } impl Component for ViewLessonPlans { type Message = Msg; type Properties = (); fn create(ctx: &Context<Self>) -> Self { // 在组件创建时发起异步请求,通过send_future自动处理生命周期 ctx.link().send_future(async { match get_lesson_plans().await { Ok(lesson_plans) => { let plans = lesson_plans.iter().map(|(_id, lp)| lp.clone()).collect(); Msg::LoadSuccess(plans) } Err(_) => Msg::LoadError, } }); Self { lesson_plans: vec![], loading_condition: ComponentLoadingStage::Loading, } } fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool { // 处理消息,更新组件状态并触发重渲染 match msg { Msg::LoadSuccess(plans) => { self.lesson_plans = plans; self.loading_condition = ComponentLoadingStage::Success; true } Msg::LoadError => { self.loading_condition = ComponentLoadingStage::Error; true } } } fn view(&self, _ctx: &Context<Self>) -> Html { match self.loading_condition { ComponentLoadingStage::Loading => html! { <h1>{ "Lesson Plans Loading" }</h1> }, ComponentLoadingStage::Success => self.lesson_plans.iter() .map(|lp| html! { <ViewLessonPlan lesson_plan={lp.clone()} /> }) .collect::<Html>(), ComponentLoadingStage::Error => html! { <h1>{ "There was an error loading the lesson plans!" }</h1> }, } } }
关键改动说明:
- 新增
Msg枚举类型,用于封装API请求的成功/失败结果 - 使用
ctx.link().send_future()替代wasm_bindgen_futures::spawn_local,该方法会自动处理异步任务与组件的生命周期绑定,无需手动满足'static要求 - 在
update方法中处理消息,更新组件状态并返回true触发重渲染 - 将API请求移至
create方法,更早发起请求减少等待时间
函数组件的短暂错误状态修复
你提到的函数组件刷新时短暂显示错误状态,原因是每次组件重渲染都会重新执行spawn_local发起请求。使用use_effect_with_deps可以限制请求只在首次渲染时执行:
#[function_component(ViewLessonPlans)] pub fn view_lesson_plans() -> Html { let state = use_state(|| ComponentLoadingStage::Loading); // 使用use_effect_with_deps确保请求仅在首次渲染时执行 use_effect_with_deps( move |_| { let state_clone = state.clone(); wasm_bindgen_futures::spawn_local(async move { match get_lesson_plans().await { Ok(lesson_plans) => { state_clone.set(ComponentLoadingStage::Success( lesson_plans.iter().map(|(_id, lp)| lp.clone()).collect(), )); } Err(_) => { state_clone.set(ComponentLoadingStage::Error); } } }); || () // 无需清理操作 }, (), // 依赖为空,仅执行一次 ); match &*state { ComponentLoadingStage::Loading => html! { <h1>{ "Lesson Plans Loading" }</h1> }, ComponentLoadingStage::Success(plans) => plans.iter() .map(|lp| html! { <ViewLessonPlan lesson_plan={lp.clone()} /> }) .collect::<Html>(), ComponentLoadingStage::Error => html! { <h1>{ "There was an error loading the lesson plans!" }</h1> }, } }
修复说明:
use_effect_with_deps接收依赖参数,当依赖变化时才会重新执行闭包- 传递空元组
()作为依赖,确保闭包仅在组件首次渲染时执行一次,避免重复发起请求导致的状态异常
内容的提问来源于stack exchange,提问作者Benjamin Peinhardt
相关产品推荐
相关产品推荐

