You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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> },
        }
    }
}

关键改动说明:

  1. 新增Msg枚举类型,用于封装API请求的成功/失败结果
  2. 使用ctx.link().send_future()替代wasm_bindgen_futures::spawn_local,该方法会自动处理异步任务与组件的生命周期绑定,无需手动满足'static要求
  3. 在update方法中处理消息,更新组件状态并返回true触发重渲染
  4. 将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 04:18:14