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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 05:57:11