React中如何避免无限循环与API重复调用?
解决方案
问题根源分析
- 无限重渲染:你直接在组件渲染流程中调用
setSelectedDataFromTabA和setSelectedDataFromTabB,每次setState都会触发组件重新渲染,渲染时又会再次调用setState,形成死循环。 - 重复API调用:你的
useFetch接收的是getData()这类直接执行的函数调用——组件每次渲染时,getData()都会被执行一次,发起新的API请求。原useFetch的useEffect依赖为空,无法正确控制请求触发时机,加剧了重复调用问题。
分步修复
1. 修复useFetch Hook
修改Hook,让它接收API调用函数本身而非直接执行后的Promise,避免组件渲染时重复发起请求,同时通过依赖控制请求触发时机:
function useFetch(serviceFunction) { const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(false); const [data, setData] = React.useState(null); React.useEffect(() => { const fetchData = async () => { try { setLoading(true); const response = await serviceFunction(); // 在Hook内部执行API调用函数 if (response.status !== 200) { throw new Error('请求失败'); } setData(response.data); setError(false); } catch (err) { setError(true); setData(null); } finally { setLoading(false); } }; fetchData(); }, [serviceFunction]); // 依赖API调用函数,确保函数变化时重新请求 return { loading, error, data }; }
2. 优化组件逻辑(两种可选方案)
方案一:用useMemo计算派生状态(推荐)
过滤后的数据属于派生状态,不需要存到state里,直接用useMemo基于原始数据计算即可,避免不必要的state更新:
const MyComponent = ({}) => { // 传入API调用函数本身,而非执行后的结果 const { data, loading, error } = useFetch(getData); const { data: dataTabA, loading: loadingTabA, error: errorTabA } = useFetch(getDataTabA); const { data: dataTabB, loading: loadingTabB, error: errorTabB } = useFetch(getDataTabB); // 处理加载和错误状态 if (loading || loadingTabA || loadingTabB) return <div>loading</div>; if (error || errorTabA || errorTabB) return <div>error</div>; // 用useMemo缓存过滤结果,仅当依赖变化时重新计算 const selectedDataFromTabA = React.useMemo(() => { return dataTabA.filter(x => x.IdTabA === data[0]?.IdTabA); }, [data, dataTabA]); const selectedDataFromTabB = React.useMemo(() => { return dataTabB.filter(x => x.IdTabB === data[0]?.IdTabB); }, [data, dataTabB]); return ( <div> {/* 在这里使用selectedDataFromTabA和selectedDataFromTabB */} </div> ); };
方案二:用useEffect更新state(如果必须存到state)
如果你因为业务需求需要把过滤后的数据存到state里,用useEffect并设置正确的依赖,确保只有当原始数据变化时才更新state:
const MyComponent = ({}) => { const [selectedDataFromTabA, setSelectedDataFromTabA] = React.useState([]); const [selectedDataFromTabB, setSelectedDataFromTabB] = React.useState([]); const { data, loading, error } = useFetch(getData); const { data: dataTabA, loading: loadingTabA, error: errorTabA } = useFetch(getDataTabA); const { data: dataTabB, loading: loadingTabB, error: errorTabB } = useFetch(getDataTabB); if (loading || loadingTabA || loadingTabB) return <div>loading</div>; if (error || errorTabA || errorTabB) return <div>error</div>; React.useEffect(() => { // 仅当data、dataTabA变化时执行过滤并更新state setSelectedDataFromTabA(dataTabA.filter(x => x.IdTabA === data[0]?.IdTabA)); }, [data, dataTabA]); React.useEffect(() => { setSelectedDataFromTabB(dataTabB.filter(x => x.IdTabB === data[0]?.IdTabB)); }, [data, dataTabB]); return ( <div> {/* 使用state中的数据 */} </div> ); };
额外注意点
- 加入可选链
data[0]?.IdTabA,避免data[0]为undefined时抛出错误。 - 如果
getData、getDataTabA等函数是在组件内部定义的,记得用useCallback包裹,避免每次渲染生成新函数导致useFetch重复请求。
内容的提问来源于stack exchange,提问作者user10863293
相关产品推荐
相关产品推荐

