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

Yew框架中分页与REST API的状态管理问题排查

问题分析与解决方案

核心问题根源

  1. 切换页码旧数据残留:异步请求存在竞态,旧请求完成后会覆盖新请求的状态,且请求发起前未清空原有数据。
  2. 切换token_address时page未重置:路由切换时Yew会复用Collection组件,内部page状态不会自动重置;之前误用use_effect_with_deps的teardown(teardown是effect销毁时执行,而非props变化时),导致page被强制设为1。

具体修复方案

1. 监听token_address变化,重置状态

添加单独的effect监听token_address的变化,当地址切换时重置page到1并清空mint:

#[function_component(Collection)]
pub fn collection_function_component(props: &Props) -> Html {
    let page = use_state(|| 1);
    let mint = use_state(|| None);

    // 监听token_address变更,重置分页和数据状态
    {
        let page = page.clone();
        let mint = mint.clone();
        let current_token = props.token_address.clone();
        use_effect_with_deps(move |prev_token| {
            // 首次渲染时prev_token为None,仅当token实际变化时执行重置
            if let Some(prev) = prev_token {
                if prev != &current_token {
                    page.set(1);
                    mint.set(None);
                }
            }
            || () // 无需清理资源,返回空teardown
        }, props.token_address.clone());
    }

    // 原有数据请求逻辑修改
    {
        let token_address = props.token_address.clone();
        let mint = mint.clone();
        let page = page.clone();
        use_effect_with_deps(move |_| {
            let req_token = token_address.clone();
            let req_page = *page;
            // 请求发起前先清空数据,解决旧数据残留
            mint.set(None);

            wasm_bindgen_futures::spawn_local(async move {
                match api_utils::fetch_single_api_response::<MintData>(
                    format!("/mint/mints?token_address={}&page={}", req_token, req_page).as_str(),
                ).await {
                    Ok(fetched_mint) => {
                        // 竞态检查:仅当当前状态与请求时一致才更新,避免旧请求覆盖新数据
                        if *page == req_page && props.token_address == req_token {
                            mint.set(Some(fetched_mint));
                        }
                    }
                    Err(e) => error!("{e}"),
                }
            });
            || ()
        }, (props.token_address.clone(), *page));
    }

    // 后续渲染逻辑
    html! {
        // ...
    }
}

2. 为什么之前的teardown方案错误

  • teardown的执行时机是当前effect被销毁时(比如依赖变化触发新effect、组件卸载),而非props变化时。当你在teardown中设置page.set(1),每次页码变化触发新effect时,旧effect的teardown都会把page重置为1,导致页码无法正常切换。
  • teardown的正确用途是清理资源(如取消异步请求),而非修改组件状态。

额外优化:用AbortController取消旧请求

如果请求耗时较长,可添加AbortController取消未完成的旧请求,进一步避免竞态:

use web_sys::AbortController;

// 在请求effect中修改:
use_effect_with_deps(move |_| {
    let req_token = token_address.clone();
    let req_page = *page;
    mint.set(None);

    let controller = AbortController::new().unwrap();
    let signal = controller.signal();

    wasm_bindgen_futures::spawn_local(async move {
        // 假设fetch_single_api_response支持传入signal参数
        match api_utils::fetch_single_api_response::<MintData>(
            format!("/mint/mints?token_address={}&page={}", req_token, req_page).as_str(),
            Some(&signal),
        ).await {
            Ok(fetched_mint) => {
                if *page == req_page && props.token_address == req_token {
                    mint.set(Some(fetched_mint));
                }
            }
            Err(e) => error!("{e}"),
        }
    });

    // teardown中取消请求
    move || {
        controller.abort().unwrap();
    }
}, (props.token_address.clone(), *page));

内容的提问来源于stack exchange,提问作者Enigo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 03:43:26