Yew框架中分页与REST API的状态管理问题排查
问题分析与解决方案
核心问题根源
- 切换页码旧数据残留:异步请求存在竞态,旧请求完成后会覆盖新请求的状态,且请求发起前未清空原有数据。
- 切换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 != ¤t_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
相关产品推荐
相关产品推荐

