React新手求助:无依赖useEffect多次调用API问题及替代方案
原因分析
React严格模式(Strict Mode)的开发环境行为
在开发环境下,React Strict Mode会主动重复调用副作用函数(包括useEffect)、组件构造函数等,目的是帮你检测潜在bug——比如未清理的订阅、内存泄漏、重复请求导致的逻辑问题。这种重复调用只会在开发环境出现,生产环境不会触发。组件重复挂载
如果父组件(/pages/dashboard/speaking/[slug].js)因为slug、userData等变量频繁变化导致重新渲染,可能会让StudentRepeatSentece组件被卸载后重新挂载,进而触发useEffect再次执行。代码笔误引发的潜在问题
你的StudentRepeatSentece组件里存在笔误:定义的状态更新函数是setPara,但实际调用了不存在的setData,这会导致para状态始终未被更新,可能引发组件不必要的重复渲染,甚至后续逻辑异常。
可行解决方案
方案1:用状态标记避免重复请求
添加一个isFetched状态,确保API只被调用一次:
export function StudentRepeatSentece(params) { const [para, setPara] = useState(); const [isFetched, setIsFetched] = useState(false); useEffect(() => { // 已经请求过就直接返回 if (isFetched) return; const handlePara = async () => { try { console.log(">>>>>>>>>>>>>>>>fetching Have Been Start"); const res = await fetch("/api/speaking/read-aloud"); const data = await res.json(); setPara(data.para); // 修正笔误:setData改为setPara setIsFetched(true); } catch (err) { console.error("请求失败:", err); } }; handlePara(); }, [isFetched]); return (<> <SecondComponent myPara={para}/> </> ); }
方案2:用AbortController取消重复请求
针对Strict Mode的重复调用,使用AbortController取消第一次未完成的请求,避免无效请求:
export function StudentRepeatSentece(params) { const [para, setPara] = useState(); useEffect(() => { const controller = new AbortController(); const signal = controller.signal; const handlePara = async () => { try { console.log(">>>>>>>>>>>>>>>>fetching Have Been Start"); // 把signal传入fetch,用于取消请求 const res = await fetch("/api/speaking/read-aloud", { signal }); const data = await res.json(); setPara(data.para); // 修正笔误 } catch (err) { // 忽略取消请求的错误 if (err.name !== 'AbortError') { console.error("请求失败:", err); } } }; handlePara(); // 组件卸载或副作用重新执行时,取消请求 return () => controller.abort(); }, []); return (<> <SecondComponent myPara={para}/> </> ); }
方案3:避免组件不必要的重复挂载
在父组件中用useMemo缓存渲染结果,只有当userData.role或slug变化时才重新生成组件:
} else if(slug == "repeat-sentence") { const TargetComponent = useMemo(() => { if (userData.role == "Admin") { return <AdminRepeatSentence />; } else if(userData.role == "Teacher") { return <TeacherRepeatSentece />; } else if(userData.role == "Student") { return <StudentRepeatSentece />; } }, [userData.role, slug]); // 依赖项:只有这两个值变了才重新渲染 return TargetComponent;
额外修正:修复状态更新的笔误
一定要把StudentRepeatSentece里的setData(data.para)改成setPara(data.para),否则para状态永远不会更新,子组件SecondComponent也拿不到正确的myPara值。
内容的提问来源于stack exchange,提问作者shivam dwivedi

