React/TypeScript栈下useEffect异步任务清理问题咨询(含React Query/Axios)
问题根因
这个报错的触发条件是组件已经卸载后,仍执行了状态更新操作,你的代码里存在两个核心问题:
- 你在
getAlertsFunction的请求回调里直接调用了setFetchData、setDisplayData,如果请求还没返回时组件就卸载,回调执行时就会触发警告 - 当前
useEffect的清理逻辑完全无效,你只是新建了一个空的CancelToken实例,没有关联到正在发送的请求,也没有执行取消操作
最优解决方案(推荐)
React Query本身已经封装了请求结果的存储、缓存、卸载逻辑,你完全不需要自定义useState存储请求返回数据,直接用useQuery返回的data字段即可,修改后代码如下:
const queryKey = 'planes'; const getAlertsFunction = async (): Promise<IData[]> => { await new Promise(resolve => setTimeout(resolve, 1000)) const response = await axios.get<IData[]>('https://xxxxx').then(res => res.data); console.log('Latest response is', response); return response; } // 直接从useQuery取返回的数据,不需要自己维护state const { data: fetchData = [], isLoading, error } = useQuery(queryKey, getAlertsFunction, { refetchOnWindowFocus: true, }); // 如果需要displayData可以直接基于fetchData派生,或者用useMemo缓存 const displayData = fetchData;
这个方案从根源上避免了自行编写setState带来的卸载后更新问题,React Query内部会处理组件卸载后的请求取消、状态更新拦截逻辑。
现有代码兼容修改方案
如果你一定要保留自行维护的state,按如下步骤修改:
- 把axios的cancelSource实例逻辑调整为可被清理函数访问
- 组件卸载时主动取消未完成的请求,同时调整
useEffect的依赖项避免重复触发清理
修改后代码如下:
const [fetchData, setFetchData] = React.useState<IData[]>([]); const [displayData, setDisplayData] = React.useState<IData[]>([]); const queryKey = 'planes'; const getAlertsFunction = async (): Promise<any> => { const source = axios.CancelToken.source() // 把cancel方法挂载到queryFn上,方便后续调用 getAlertsFunction.cancel = () => source.cancel('Query cancelled') await new Promise(resolve => setTimeout(resolve, 1000)) try { const response = await axios.get('https://xxxxx', { cancelToken: source.token }).then(res => res.data); setFetchData(response); setDisplayData(response); console.log('Latest response is', response); return response; } catch (err) { // 捕获取消请求的报错,避免异常抛出 if (axios.isCancel(err)) return; throw err; } } const queryInfo: any = useQuery(queryKey, getAlertsFunction, { refetchOnWindowFocus: true, }); useEffect(() => { console.log('Mounted'); return () => { console.log('Un-mounted'); // 组件卸载时调用取消请求的方法 getAlertsFunction?.cancel?.() }; }, []);
注意这里useEffect的依赖项要改为空数组,你原来写的[displayData]会导致每次displayData变化都重新执行effect、生成新的清理函数,不符合卸载时只执行一次清理的需求。
内容的提问来源于stack exchange,提问作者Sole
相关产品推荐
相关产品推荐

