Next.js中Fetch API重复调用与组件重复渲染问题求助
Next.js项目中的两个问题:Fetch API重复调用与Quiz组件二次渲染
问题描述
- 尽管在
Question组件中设置了加载状态判断,fetch("/api/quiz")仍会被调用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
相关产品推荐
相关产品推荐

