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

React首页从其他路由返回时重复渲染(重复调用API)问题排查

从其他路由返回首页时组件重复渲染并重复调用API的原因

我遇到一个问题:从其他路由返回首页时,React组件会重新渲染并重复调用API。按我的理解,用Link标签跳转页面时React应该保留之前的页面状态,请问是什么原因导致的重复渲染?


首页组件

const Homepage = () => {
  const [header, setHeader] = useState([]);
  const [imageindex, setImageindex] = useState(0);
  const [error, setError] = useState("");

  const increaseIndex = () => {
    setImageindex((imageindex + 1) % header.length);
  };

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await axios.get(`${baseurl}`);
        setHeader(response.data);
      } catch (error) {
        setError("Error fetching data:", error);
      }
    };
    fetchData();
  }, []);

  useEffect(() => {
    const imageTransition = setTimeout(increaseIndex, 4000);
    return () => clearTimeout(imageTransition);
  }, [imageindex, header]);

  return (
    <div>
      <Helmet>
        <title>The Third Front</title>
        <meta name="description" content="Homepage of a satirical news blog" />
      </Helmet>
      <AbsoluteNavbar className="absnav">
        <Navbar />
      </AbsoluteNavbar>
      {error && <p style={{ color: "red" }}>{error}</p>}
      <div className="header-wrapper">
        {header &&
          header.map((index) => (
            <Link
              to={`/news/${index.slug}`}
              className="header"
              key={index.img.src}
              style={{ translate: `${-100 * imageindex}%` }}
            >
              <div
                className="background-layer"
                style={{
                  backgroundImage: index
                    ? `linear-gradient(to bottom, rgba(0, 0, 0, 0) 20%, rgba(0, 0, 0, 1)), url(${index.img.src})`
                    : "",
                  backgroundPosition: index.img.position
                    ? `${index.img.position}`
                    : "center",
                }}
              ></div>
              <div className="text-layer">
                <Hero heading={index.heading} author={index.author} />
              </div>
            </Link>
          ))}
      </div>
      <MainContent />
      <Footer />
    </div>
  );
};

export default Homepage;

MainContent组件

/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect } from "react";
import axios from "axios";
import "./main-content.css";
import { Link } from "react-router-dom";
const baseurl = import.meta.env.VITE_BASE_URL;


const MainContent = () => {
  const [data, setData] = useState([]);
  const [sidebardata, setSidebardata] = useState([])
  const [mobileView, setMobileView] = useState();
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(true);
  const [cat, setCat] = useState();
  const [catdata, setCatdata] = useState([])
  const [error, setError] = useState('');

  const fetchInfo = async() => {
    setLoading(true);
    const link = cat? `${baseurl}/?page=${page}&cat=${cat}`: `${baseurl}/?page=${page}`
    axios
      .get(link)
      .then((response) => {
        setData((prevNews) => [...prevNews, ...response.data]);
        setLoading(false);
      })
      .catch((error) => {
        setError("Error fetching data:", error);
      });
  };

  const handleLoadMore = () => {
    setPage(page + 1);
  };

  const handleResize = () => {
    if (window.innerWidth < 1080) {
      setMobileView(true);
    } else {
      setMobileView(false);
    }
  };

  const handleCatchange = (e) =>{
    setCat(e.target.value)
    setPage(1);
    setData([]);
  }

  useEffect(() => {
    fetchInfo();
  }, [page, cat]);

  useEffect(() => {
    const sidebarlink = `${baseurl}/sidebar`;
    axios
      .get(sidebarlink)
      .then((response) => {
        setSidebardata((prevNews) => [...prevNews, ...response.data]);
      })
      .catch((error) => {
        setError("Error fetching sidebar data:", error);
      });
    axios
    .get(`${baseurl}/categories`)
    .then((response) => {
      setCatdata(response.data)
    })
    handleResize();
    window.addEventListener("resize", handleResize);
    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, []);
  
  return (
    <div className="main">
    {error && <p>{error}</p>}
      <div className="content-wrap">
        <div className="select">
          <h2>Sort News By categories</h2>
          <select  onChange={handleCatchange}> 
              <option value="">All</option>
              {catdata.map((e, index) => (
                <option key={index} value={e.title}>{e.title}</option>
              ))}
            </select>
        </div>
        <div className="content">
          {data.map((e, index) => {
            const itemWrapClass = index % 2 === 0 ? "itemwrap1" : "itemwrap2";
            return (
              <Link to={`/news/${e.slug}`} className="item" key={index}>
                <div className={itemWrapClass}>
                    <div className="gradient">
                    <img id="img" src={e.img.src} alt="News Image" />
                    </div>
                    <div className="heading-p">
                    <hr />
                    <h2>{e.heading}</h2>
                    <p>
                      {e.subheading}
                    </p>
                    <div className="catog">
                      Categories:
                      {e.category ? (
                        e.category.map((cat) => (
                          <span className='px-1 text-sky' key={cat._id}>{cat.title}</span>
                        ))
                      ) : (
                        <span>No categories available</span>
                      )}
                    </div>
                  </div>
                </div>
              </Link>
            );
          })}
          <div className="loading-wrap">
            {loading ? (
              <div className="lds-ring">
                <div></div>
                <div></div>
                <div></div>
                <div></div>
              </div>
            ) : (
              <button id="loadmore" onClick={handleLoadMore}>
                Load More
              </button>
            )}
          </div>
        </div>
      </div>
    </div>
  );
};

export default MainContent;

新闻详情页组件

import React from "react";
import Navbar from "../navbar/navbar";
import Footer from "../footer";
import "./newspage.css";
import { useState, useEffect } from "react";
import axios from "axios";
import { useParams } from "react-router-dom";
import { Helmet } from "react-helmet";
const baseurl = import.meta.env.VITE_BASE_URL;

const Newspage = () => {
  const [data, setData] = useState([]);
  const { slug } = useParams();
  useEffect(() => {
    axios
      .get(`${baseurl}/news/${slug}`)
      .then((response) => {
        setData(response.data[0]);
      })
      .catch((error) => {
        console.error("Error fetching data:", error);
        setData(null);
      });
  });

  const renderHtmlContent = (html) => {
    return { __html: html };
  };

  if (!data) {
    throw new Error("Data not found");
  }
  return (
    <>
      <Helmet>
        <title>{data.heading}</title>
        <meta name="description" content="News page" />
      </Helmet>
      <div
        className="header"
        style={{
          backgroundImage: data.img ? `url(${data.img.src})` : "",
          backgroundPosition: data.img ? `${data.img.position}` : "center",
        }}
      >
        <Navbar />
      </div>
      <div className="newsbody">
        <div className="news-hero-text">
          <h1>{data.heading}</h1>
          <p className="subheading">{data.subheading}</p>
          <p>
            <em>{data.author}</em>
          </p>
        </div>
      </div>
    </>
  );
};

export default Newspage;

React路由配置

import { RouterProvider, createBrowserRouter} from "react-router-dom";
import Homepage from './homepage/homepage';
import Newspage from './newspage/newspage';

const Router = () =>{
  const router = createBrowserRouter([
    {
      path: "/",
      element: <Homepage/>
    },
    {
      path: "/news/:slug",
      element: <Newspage/>,
      errorElement: <NotFound/>
    },

  ]);
  return <RouterProvider router={router} />;
}

export default Router;

问题原因分析

  1. 组件卸载与重新挂载:React Router的默认逻辑是,路由切换时离开的路由组件会被完全卸载,进入的路由组件重新挂载。所以从详情页返回首页时,Homepage及其子组件MainContent都会被重新挂载,触发组件初始化流程。

  2. useEffect执行逻辑:你在Homepage和MainContent中使用了空依赖数组的useEffect,这类钩子会在组件挂载时执行一次。当组件重新挂载,这些useEffect会再次运行,导致API被重复调用。

  3. 无状态持久化机制:首页的状态(如新闻列表、轮播数据)仅存储在组件内部,组件卸载后状态直接丢失,重新挂载时只能重新请求数据。

解决思路

  • 组件缓存:使用第三方库实现类似keep-alive的功能,让首页组件在路由切换时不被卸载,保留内部状态。
  • 全局状态管理:将首页需要的数据存入Context或Redux这类全局状态容器,首次请求后复用全局数据,避免重复请求。
  • 本地存储缓存:请求成功后将数据存入localStorage或sessionStorage,组件挂载时优先读取本地数据,不存在再发起请求,还可设置缓存过期时间。
  • 路由级别预加载:利用React Router v6的loader函数在路由层面预加载数据,数据会被路由缓存,返回首页时直接复用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 22:47:01