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

Next.js中Fetch API重复调用与组件重复渲染问题求助

Next.js项目中的两个问题:Fetch API重复调用与Quiz组件二次渲染

问题描述

  1. 尽管在Question组件中设置了加载状态判断,fetch("/api/quiz")仍会被调用2次及以上
  2. quiz.js组件出现二次渲染的情况,控制台日志重复输出

相关代码

文件名:question.js

export default function Question() {
    const [quiz, setQuiz] = useState("");
    const [ loading , setLoading ] = useState(true);

    useEffect(() => {

        const fetchData = async () => {

            if(loading) {
                  const response = await fetch("/api/quiz");
                  const data = await response.json();

                 setQuiz(data.message);
                 setLoading(false);

           } else {
                 setLoading(false);
     }

   }

   fetchData();
}, []);


 if (loading) {
     return <> </>;
 }

return (
<>
  <Head>
    <title>{`Quiz - Play And Win with Quizy`}</title>
    <meta name="description" content="Login Page" />
    <link rel="icon" href="/favicon.ico" />
  </Head>

  <section className="mobile start">
    {/* <span> {quiz} </span> */}
    {
      !loading ? <Quiz totalQuiz={2} quizy={quiz} /> : <></>
     }
  </section>
</>
);
}

文件名:quiz.js

export default function Quiz({ totalQuiz, quizy }) {
     // set First Quiz
     const [quiz_no, setQuizNo] = useState(1); // current quiz no
     const router = useRouter();


     console.log("Line 12 : File Name : ./components/quiz.js");
     console.log(quizy);

     if (typeof window !== 'undefined') {
        console.log("For Client Side Only");
     }

     const handleClick = (ans) => (e) => {
         e.preventDefault();

         if(quizy[0][quiz_no - 1].quizAnswer == ans) {
               e.currentTarget.classList.add('true');

              setTimeout(() => {
    
                 if (quiz_no < totalQuiz) {
                      e.currentTarget.classList.remove('true');

                      setQuizNo(quiz_no + 1);
                } else {
                      router.push("/start");
                }
        }, 1000);
   } else {
        e.currentTarget.classList.add('false');

  setTimeout(() => {
      e.currentTarget.classList.remove('false');
      if (quiz_no < totalQuiz) {
         setQuizNo(quiz_no + 1);
      } else {
         router.push("/start");
      }
    }, 1000);
  }
}

问题原因与解决方案

1. Fetch API重复调用

原因:

  • Next.js开发模式下的Strict Mode会重复执行useEffect等副作用函数,用于检测潜在bug(比如未清理的副作用),这会触发fetch逻辑二次执行。
  • 组件初始化时loading为true,Strict Mode的二次执行会重新初始化组件状态,导致loading再次变为true,进而重复调用fetch。

解决方案:

  • 方案一:用useRef标记请求已执行
    通过useRef创建标记变量,确保fetch仅执行一次:
    export default function Question() {
        const [quiz, setQuiz] = useState("");
        const [ loading , setLoading ] = useState(true);
        const hasFetched = useRef(false); // 新增执行标记
    
        useEffect(() => {
            const fetchData = async () => {
                if(!hasFetched.current && loading) {
                    hasFetched.current = true; // 标记已执行
                    const response = await fetch("/api/quiz");
                    const data = await response.json();
                    setQuiz(data.message);
                    setLoading(false);
                } else {
                    setLoading(false);
                }
            }
            fetchData();
        }, []);
    
        // 其余代码不变
    }
    
  • 方案二:改用服务器端数据获取
    将fetch逻辑移到getServerSideProps中,请求仅在服务器端执行一次,客户端直接接收数据:
    export async function getServerSideProps() {
        const response = await fetch("http://localhost:3000/api/quiz"); // 需填写完整接口URL
        const data = await response.json();
        return { props: { quiz: data.message } };
    }
    
    export default function Question({ quiz }) {
        const [ loading , setLoading ] = useState(false); // 直接关闭加载状态
    
        if (loading) {
            return <> </>;
        }
    
        return (
            <>
                <Head>
                  <title>{`Quiz - Play And Win with Quizy`}</title>
                  <meta name="description" content="Login Page" />
                  <link rel="icon" href="/favicon.ico" />
                </Head>
                <section className="mobile start">
                    <Quiz totalQuiz={2} quizy={quiz} />
                </section>
            </>
        );
    }
    

2. Quiz组件二次渲染

原因:

  • 开发模式下Strict Mode会强制组件二次渲染以检测问题;同时Question组件中quiz状态从空字符串更新为接口数据时,也会触发Quiz组件重渲染,两者叠加导致日志重复。

解决方案:

  • 方案一:用React.memo优化组件渲染
    当Quiz组件的props未变化时,阻止不必要的重渲染:
    import { memo } from 'react';
    
    const Quiz = ({ totalQuiz, quizy }) => {
        // 组件原有代码不变
    }
    
    export default memo(Quiz);
    
  • 方案二:忽略开发模式的二次渲染
    Strict Mode的二次渲染仅存在于开发环境,生产环境下不会出现该问题,若只是开发时日志重复,可无需特殊处理,这是React的正常检测机制。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 18:48:38