React调用API后setState更新延迟导致二次请求失败、组件崩溃如何解决?
问题根因
你代码里的问题主要出在这几个地方:
- 渲染逻辑无兜底:
secondData初始值是空字符串,直接访问secondData.blabla不存在的属性会直接导致组件崩溃 - 第二接口URL错误:请求地址末尾多余拼接了
HTTP/1.1字段,会直接导致接口请求失败 - 位置权限错误未处理:
navigator.geolocation.getCurrentPosition没有加错误回调,如果用户拒绝位置授权,整个逻辑会卡住,loading状态一直为true也不会抛出错误 - 无副作用清理:如果组件在请求过程中卸载,未完成的请求回调执行setState会触发内存泄漏警告
third函数每次组件重渲染都会重新创建,存在冗余开销
修复方案
按以下调整即可解决所有问题:
- 渲染前先判断
loading和secondData的状态,存在有效值再访问对应属性 - 删除第二个接口URL末尾多余的
HTTP/1.1 - 补充位置授权的错误回调逻辑
- 用
AbortController处理请求终止,避免内存泄漏 - 把
third函数移到对应的useEffect内部,减少不必要的重复创建
完整修复后代码
const SecondDisplay = () => { const [firstData, setFirstData] = useState("") const [secondData, setSecondData] = useState("") const [errors, setErrors] = useState("") const [loading, setLoading] = useState(true) useEffect(() => { const controller = new AbortController() const signal = controller.signal const handleLocationError = (error) => { setErrors("位置权限获取失败,请授权后重试") setLoading(false) } navigator.geolocation.getCurrentPosition(function(position) { fetch(`https://dataservice.accuweather.com/locations/v1/cities/geoposition/search?apikey=AZvK08ugMNLlAGAwDD9GQGj108Tm8OIP&q=${position.coords.latitude}%2C${position.coords.longitude}&language=en-us&details=false&toplevel=false`, { signal }) .then(res => { if(!res.ok){ throw Error("Sorry, something went wrong. please try again later.") } return res.json() }) .then(data => { setFirstData({key: data.Key, name: data.AdministrativeArea.EnglishName}) }) .catch(error => { if (error.name !== 'AbortError') { setErrors(error.message) setLoading(false) } }) }, handleLocationError) // 清理函数,组件卸载时终止请求 return () => controller.abort() }, []) useEffect(() => { if(firstData === "") return const controller = new AbortController() const signal = controller.signal fetch(`http://dataservice.accuweather.com/currentconditions/v1/${firstData.key}?apikey=AZvK08ugMNLlAGAwDD9GQGj108Tm8OIP&language=en-us&details=true`, { signal }) .then(res => { if(!res.ok){ throw Error("Sorry, something went wrong. please try again later.") } return res.json() }) .then(data => { setSecondData(data) setLoading(false) }) .catch(error => { if (error.name !== 'AbortError') { setErrors(error.message) setLoading(false) } }) return () => controller.abort() }, [firstData]) if (loading) return <p>加载中...</p> if (errors) return <p>{errors}</p> return(<p>{secondData?.blabla || "暂无数据"}</p>) }
注:
secondData?.blabla是可选链语法,会在secondData为空的时候直接返回undefined,不会抛出异常,配合||做兜底展示即可。
内容的提问来源于stack exchange,提问作者BarakOren
相关产品推荐
相关产品推荐

