Next.js页面快速切换cid时渲染错误数据问题求助
问题:Next.js页面切换查询参数时渲染错误数据
页面基于查询参数cid展示客户信息,通过Axios从API获取数据。快速切换不同cid(如1→2→1)时,页面偶尔显示错误的cid对应数据,比如切回cid=1后仍显示cid=2的内容。
原因分析
核心问题是异步请求竞态:
- 快速切换
cid时,前一个cid的异步请求尚未完成,新请求已发起。若旧请求晚于新请求完成,会用旧数据覆盖新状态,导致渲染错误。 - 当前代码中
AbortController的使用错误:在finally里直接调用controller.abort(),会在请求完成后立刻中止,无法起到取消未完成请求的作用。
解决方案
1. 正确使用AbortController取消旧请求
在useEffect中创建控制器,当cid变化时,先取消上一次未完成的请求,再发起新请求:
useEffect(() => { const controller = new AbortController(); const { signal } = controller; const fetchData = async () => { try { if (router.query.cid) { // 并行请求优化,同时获取两个数据 const [customerData, groupsData] = await Promise.all([ fetchCustomer(router.query.cid, signal), fetchGroups(router.query.cid, signal) ]); setCustomer(customerData); setGroups(groupsData); } } catch (error) { // 忽略取消请求的错误 if (error.name !== 'AbortError') { console.error('Error fetching customer or groups:', error); } } }; fetchData(); // 组件卸载或cid变化时,取消未完成的请求 return () => controller.abort(); }, [router.query.cid]); // 修改fetch函数,接收signal参数 const fetchCustomer = async (cid, signal) => { try { const res = await axios.get(`/api/customer?cid=${cid}`, { signal, ...config }); return res.data[0]; } catch (err) { console.error(err); throw err; } }; const fetchGroups = async (cid, signal) => { try { const res = await axios.get(`/api/customer/groups?cid=${cid}`, { signal, ...config }); return res.data; } catch (error) { console.error(error); throw error; } };
2. 添加请求结果校验(双重保险)
即使请求未被取消,更新状态前校验当前cid是否与请求时一致:
useEffect(() => { const currentCid = router.query.cid; // 保存请求时的cid const fetchData = async () => { try { if (currentCid) { const customerData = await fetchCustomer(currentCid); const groupsData = await fetchGroups(currentCid); // 校验当前cid是否未变化,再更新状态 if (router.query.cid === currentCid) { setCustomer(customerData); setGroups(groupsData); } } } catch (error) { if (error.name !== 'AbortError') { console.error('Error fetching customer or groups:', error); } } }; fetchData(); }, [router.query.cid]);
3. 优化加载状态(可选)
添加加载状态,避免切换时显示旧数据:
const [isLoading, setIsLoading] = useState(false); useEffect(() => { const controller = new AbortController(); const { signal } = controller; const currentCid = router.query.cid; const fetchData = async () => { if (!currentCid) return; setIsLoading(true); try { const [customerData, groupsData] = await Promise.all([ fetchCustomer(currentCid, signal), fetchGroups(currentCid, signal) ]); if (router.query.cid === currentCid) { setCustomer(customerData); setGroups(groupsData); } } catch (error) { if (error.name !== 'AbortError') { console.error('Error fetching customer or groups:', error); } } finally { setIsLoading(false); } }; fetchData(); return () => controller.abort(); }, [router.query.cid]); // 渲染时优先显示加载状态 if (customers.length < 1 || isLoading) { return <Spinner />; }
内容的提问来源于stack exchange,提问作者Brandon Han
相关产品推荐
相关产品推荐

