React Router v6切换同组件路由未触发刷新重请求API问题
问题背景
当前使用React Router v6搭建路由,多篇博客复用同一个Blog组件渲染,仅URL路径不同,组件从同个API拉取对应路径的文章数据。路由切换时控制台能打印blog re ran日志,说明组件函数确实重新执行,但始终不会触发新的API请求,原有2分钟自动刷新内容的逻辑也导致路由切换时内容无法同步更新,常规的location依赖添加方案无效。
现有路由配置
import { Component, useState, useEffect } from "react"; import { Routes, Route, Link, useParams, useNavigate, useSearchParams, useLocation } from "react-router-dom"; function App() { return ( <Header Title={"My blog"}> <Routes> <Route path="/" element={<Home />} /> <Route path="blog1" element={<Blog />} /> <Route path="blog2" element={<Blog />} /> <Route path="*" element={<NotFound />} /> </Routes> </Header> ); }
现有Blog组件实现
function Blog() { console.log(`blog re ran`); const [data, setData] = useState(null); const location = useLocation(); const [count, setCount] = React.useState(0); var loaded = false; let routerpath = location.pathname; let routerpath2 = routerpath.substring(1); const timer = ms => new Promise(res => setTimeout(res, ms)) async function load () { for (var i = 0; i < 2; i++) { console.log(`Refresh timer: ${i}`); await timer(120000); // 等待2分钟 setCount(0); console.log("Count reset."); loaded = false; } } useEffect(() => { if (routerpath) { if (count == 0) { fetch(`https://api.example.com/api.php?route=${routerpath2}`) .then((Response) => { if (Response.ok) { return Response.json(); } throw Response; }) .then((data) => { setData(data); console.log("API call answered. {productId}"); loaded = false; setCount(count + 1); console.log("Count incremented."); load(); }) }, [data, count, location, location.pathname, routerpath ]); if (data) { if (loaded == false){ console.log("Reloaded content."); return ( <div> {data.BlogPosts.map((BlogPost, i) => { return ( <Typography paragraph>{BlogPost.MainContent}</Typography> ); } </div> ); load(); loaded = true; } }
问题根因
- 路由优化导致组件未卸载重建:React Router v6对同组件同位置的路由默认做复用优化,从
/blog1切到/blog2时,Blog组件不会完全卸载重置状态,原有count状态会保留上次的值(请求完成后count被设为1),因此useEffect里count == 0的请求触发条件永远不满足。 - 普通变量无法跨渲染周期保留状态:组件每次重渲染都会重新执行函数体,
var loaded = false会在每次渲染时重置,根本起不到加载锁的作用。 - 定时器逻辑存在内存泄漏+闭包问题:
load函数里的定时器没有在组件重渲染/卸载时清理,旧的定时器还在引用上一次渲染的闭包变量,会导致状态更新混乱。 - useEffect依赖冗余且触发逻辑错误:依赖里加了
data会导致请求完成后setData再次触发effect,形成不必要的重复执行;路由切换时仅location变化,但count不为0,所以不会触发请求。
修复方案
- 路由层面给同组件路由加key强制重建:给每个指向Blog的Route加唯一key,路由切换时强制卸载旧组件、挂载新组件,从根源避免状态复用问题。
- 重构状态与请求逻辑:去掉无效的count、loaded普通变量,直接用路径作为请求触发的唯一依赖,定时器单独用useEffect管理,加清理逻辑。
- 修正useEffect依赖:仅保留路径作为请求effect的依赖,定时器逻辑单独处理自动刷新。
修复后路由配置
function App() { return ( <Header Title={"My blog"}> <Routes> <Route path="/" element={<Home />} /> {/* 给每个博客路由加唯一key,路径变化时强制重建Blog组件 */} <Route path="blog1" element={<Blog key="blog1" />} /> <Route path="blog2" element={<Blog key="blog2" />} /> <Route path="*" element={<NotFound />} /> </Routes> </Header> ); }
修复后Blog组件核心逻辑
function Blog() { const [data, setData] = useState(null); const location = useLocation(); const routerpath2 = location.pathname.substring(1); // 数据请求逻辑:仅当路径变化时触发 useEffect(() => { // 定义中止控制器,组件卸载/路径变化时取消上一次请求 const controller = new AbortController(); const fetchData = async () => { try { const res = await fetch(`https://api.example.com/api.php?route=${routerpath2}`, { signal: controller.signal }); if (!res.ok) throw new Error('Request failed'); const postData = await res.json(); setData(postData); } catch (err) { if (err.name !== 'AbortError') console.error(err); } }; fetchData(); // 清理函数 return () => controller.abort(); }, [routerpath2]); // 2分钟自动刷新逻辑 useEffect(() => { const timer = setInterval(() => { // 重新拉取当前路径数据 fetch(`https://api.example.com/api.php?route=${routerpath2}`) .then(res => res.ok ? res.json() : Promise.reject(res)) .then(newData => setData(newData)) .catch(err => console.error(err)); }, 120000); // 组件卸载/路径变化时清除定时器,避免内存泄漏 return () => clearInterval(timer); }, [routerpath2]); // 加载态 if (!data) return <div>加载中...</div>; return ( <div> {data.BlogPosts.map((BlogPost, i) => ( <Typography paragraph key={i}>{BlogPost.MainContent}</Typography> ))} </div> ); }
更优的路由写法是直接用动态路由
<Route path="blog/:id" element={<Blog />} />,通过useParams()获取博客id,不需要给每个博客单独写路由规则,也可以通过params的变化触发请求。
内容的提问来源于stack exchange,提问作者mike
相关产品推荐
相关产品推荐

