如何在更新状态的 yew 结构组件内使用 wasm_bindgen_futures 发出 HTTP 请求

Ben*_*rdt 5 lifetime rust yew

我有一个 yew 结构组件,它应该向 api 发出 get 请求,然后呈现项目列表。我正在尝试在组件的 render 方法内执行请求,但遇到了生命周期问题,无法在 wasm_bindgen_future 中使用对 self 的引用。我必须使用 wasm_bindgen_future 才能执行异步 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;
                    },
                }
            });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

和生成的错误

`self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
...is used here...rustcE0759
Run Code Online (Sandbox Code Playgroud)

我如何发出此 api 请求并使用响应来更新自身?

编辑:作为参考,这是我所需功能的 function_component 版本。令人讨厌的是,它在刷新时短暂显示错误情况,不知道为什么。我对 ComponentLoadingStage 进行了一些重构,以便成功变体可以仅包含 api 响应内容,以使事情变得更简单。

#[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>}
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Bam*_*tan 3

创建异步块时,其中的任何内容都可能会被扔到另一个线程,或者可能在 2 年后被调用。这就是为什么 Rust 不允许你将引用移动到除静态引用之外的引用中。

spawn_local()函数具体指出:

future 必须是“静态的”,因为它将被安排在后台运行并且不能包含任何堆栈引用。

所以你不能使用对 self 的可变引用,但我的朋友有办法!

Yew 知道,在 UI 环境中,您需要一种在未知点(例如单击按钮)向自己发送信息的方法。

Yew 允许您创建更新方法,它们接受消息并且您可以对这些消息做出反应。

因此,您需要做的是创建一个链接,将其移动到异步块,调用该get_lesson_plans()方法,然后使用该链接发送消息。然后,您将在 update 方法中收到结果,该方法可以访问对 self 的可变引用。

它看起来像这样:

impl Component for ViewLessonPlans {
    type Message = Result<..., ...>; // plug return type of get_lesson_plans()
    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 {
            let link = ctx.link().clone();
            wasm_bindgen_futures::spawn_local(async move {
                let result = get_lesson_plans().await;
                link.send_message(result);
            });
        }
    }

    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
        match msg {
            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;
            },
        }
        true
    }
}
Run Code Online (Sandbox Code Playgroud)