React监听props变化触发useEffect出现API死循环如何解决
死循环根本原因
触发死循环的核心问题有3个:
- 渲染阶段直接调用了修改状态的API请求函数
你在loader为false的else分支里直接执行callApi(),而callApi请求成功后会调用setLoader(true)修改状态,状态修改立刻触发组件重新渲染。如果此时loader又变成false,就会再次进入else分支调用callApi,形成最直接的循环触发路径。 - useEffect逻辑和状态更新逻辑冲突
你在useEffect里同时执行callApi()和setLoader(false):callApi请求成功会把loader设为true,useEffect又把loader设为false,二者反复拉扯触发重渲染。 - props依赖引用不稳定,重复触发请求
如果父组件传递的props.coordinates是每次渲染都新生成的对象(比如父组件直接写coordinates={{lat: 1, lon: 2}}没有做memo优化),会导致useEffect的依赖每次都判定为变化,反复执行useEffect回调。同时useEffect里已经调用了一次callApi,else分支又调用一次,等于每次触发循环都会发2次请求,自然很快把API配额耗光触发封禁。
修复方案
修改后的完整代码如下:
import React, { useEffect, useState } from "react"; import axios from "axios"; import ForcastHour from "./ForcastHour"; import "./WeatherHourlyForcast.css"; const WeatherHourlyForcast = (props) => { const [loader, setLoader] = useState(false); const [hourlyForcastData, setHourlylyForcastData] = useState(null); // 仅在经纬度数值变化时触发请求,初始挂载也会执行一次 useEffect(() => { // 发起请求前先把loader设为false,避免旧数据展示 setLoader(false); callApi(); // 用基础类型做依赖比引用类型对象更稳定,避免不必要的触发 }, [props.coordinates.lat, props.coordinates.lon]); const showHourlyForcast = (response) => { console.log("showHourlyForcast", response.data.hourly); setHourlylyForcastData(response.data.hourly); setLoader(true); }; function callApi() { let latitude = props.coordinates.lat; let longitude = props.coordinates.lon; const apiKey = "23422500afd990f6bd64b60f46cf509a"; let units = "metric"; let apiUrl = `https://api.openweathermap.org/data/2.5/onecall?lat=${latitude}&lon=${longitude}&appid=${apiKey}&units=${units}`; axios.get(apiUrl).then(showHourlyForcast); console.log("hourly", apiUrl); } if (loader) { return ( <div className="row"> <div className="col-md-6"> <div className="row"> {hourlyForcastData.map(function (hourlyforcast, index) { if (index < 4 && index > 0) { return ( <div className="col-4 box-weather my-auto text-center" key={index} > <ForcastHour data={hourlyforcast} /> </div> ); } })} </div> </div> <div className="col-md-6"> <div className="row"> {hourlyForcastData.map(function (hourlyforcast, index) { if (index < 7 && index > 3) { return ( <div className="col-4 box-weather my-auto text-center" key={index} > <ForcastHour data={hourlyforcast} /> </div> ); } })} </div> </div> </div> ); } else { // 删掉这里的callApi调用,加载状态直接返回null即可,请求只在useEffect里触发 return null; } }; export default WeatherHourlyForcast;
核心修改点
- 删掉了else分支里的
callApi()调用,所有API请求只在useEffect里触发,避免渲染阶段意外调用请求 - 把useEffect的依赖从引用类型
props.coordinates改成基础类型props.coordinates.lat和props.coordinates.lon,避免父组件传递的对象引用不稳定导致的不必要触发 - 调整了useEffect里的执行顺序,先设loader为false再发请求,逻辑更符合预期
内容的提问来源于stack exchange,提问作者Maryam Mohebbi
相关产品推荐
相关产品推荐

