React组件调用Flask接口异常排查求助:一个正常一个报未挂载状态更新错误
React组件调用Flask接口异常排查求助:一个正常一个报未挂载状态更新错误
兄弟,我仔细对比了你这两个组件的代码,终于找到问题所在了!先给你拆解下为啥一个正常一个报错,再给你说怎么修复。
首先看你遇到的错误:Can't perform a React state update on a component that hasn't mounted yet,这个警告本质是异步请求完成后,组件已经被卸载了,这时候再调用状态更新函数就会触发它。
那为啥ReactLogo没报错,Checklist却中招了?咱们看两者的请求触发时机:
ReactLogo是直接在组件函数体里调用fetchData,这意味着组件每渲染一次就会发一次请求,但大概率这个组件是一直挂载在页面上的,所以请求完成时组件还在,状态更新没问题。但这种写法其实是不规范的,容易导致重复请求,只是你没遇到坑而已。Checklist是在useEffect(空依赖数组)里调用的,只在组件挂载时发一次请求。如果这个组件在请求还没返回的时候就被卸载了(比如用户快速切换路由、关闭页面之类的),请求完成后调用setShoppingList就会触发那个警告。
另外你的Checklist还有个潜在问题:初始状态useState()是undefined,组件第一次渲染时shoppingList是undefined,直接调用map会报错Cannot read property 'map' of undefined,这个你可能还没遇到,但迟早会出问题。
接下来给你具体的修复方案:
方案1:给axios请求添加取消令牌,在组件卸载时取消请求
修改fetchData函数,支持传入取消令牌,然后在useEffect的清理函数里取消请求:
import './Checklist.css' import { useEffect, useState } from 'react' import axios from "axios"; // 修改fetchData,接受cancelToken参数 const fetchData = (hook, cancelToken) => { return axios.get("http://localhost:5000/shopping", { cancelToken }) .then((response) => hook(response.data.shoppingList)) .catch(error => { // 捕获取消请求的错误,避免控制台报错 if (axios.isCancel(error)) { console.log('请求已取消:', error.message); } }); } function Checklist() { // 初始化state为空数组,避免map报错 const [shoppingList, setShoppingList] = useState([]); useEffect(() => { // 创建取消令牌 const source = axios.CancelToken.source(); fetchData(setShoppingList, source.token); // 组件卸载时取消请求 return () => { source.cancel('组件已卸载,取消请求'); }; }, []); return ( <div className='checklist-div'> <header>Shopping List</header> <div className='shopping-div'> {/* 现在因为初始是数组,map不会报错 */} {shoppingList.map((entry, index) => { return ( <div className="form-check form-switch" key={index}> <input className="form-check-input" disabled={index % 3 !== 0} {/* 简化判断逻辑,更清晰 */} id={`${entry}-input`} type="checkbox" /> <label className="form-check-label" >{entry}</label> </div> ) })} </div> </div> ); } export default Checklist;
方案2:在状态更新前判断组件是否挂载
如果不想用axios的取消令牌,也可以在组件里加个标记,判断组件是否还挂载着:
import './Checklist.css' import { useEffect, useState } from 'react' import axios from "axios"; const fetchData = () => { return axios.get("http://localhost:5000/shopping") .then((response) => response.data.shoppingList); } function Checklist() { const [shoppingList, setShoppingList] = useState([]); // 添加挂载状态标记 const [isMounted, setIsMounted] = useState(true); useEffect(() => { fetchData().then(data => { // 只有组件挂载时才更新状态 if (isMounted) { setShoppingList(data); } }); return () => { // 组件卸载时标记为未挂载 setIsMounted(false); }; }, [isMounted]); return ( <div className='checklist-div'> <header>Shopping List</header> <div className='shopping-div'> {shoppingList.map((entry, index) => { return ( <div className="form-check form-switch" key={index}> <input className="form-check-input" disabled={index % 3 !== 0} id={`${entry}-input`} type="checkbox" /> <label className="form-check-label" >{entry}</label> </div> ) })} </div> </div> ); } export default Checklist;
另外,你ReactLogo的写法其实也有问题,建议改成和修复后的Checklist一样,用useEffect+清理函数,避免后续出现类似的警告或者重复请求的问题。
备注:内容来源于stack exchange,提问作者Chubbs
相关产品推荐
相关产品推荐

