React中API返回401响应触发登出时抛出TypeError错误
问题分析与解决方案
问题根源
- 异步请求延迟导致组件卸载后执行代码:当API返回401时调用
logoutUser会触发页面导航,Profile组件被卸载,但之前发起的异步fetch请求的回调仍会继续执行,此时调用函数可能访问到已失效的上下文引用。 logoutUser函数引用不稳定:AuthContext中每次provider渲染时都会重新创建logoutUser函数,Profile组件挂载时获取的是旧版本函数,当context更新后,旧函数可能出现引用失效问题。- useEffect依赖缺失:
getProfile依赖token和logoutUser,但useEffect的依赖数组为空,导致使用的是组件挂载时的旧值/旧函数。
解决方案
1. 标记组件挂载状态,避免卸载后执行代码
在Profile组件中用useRef跟踪组件是否挂载,执行关键逻辑前先做检查:
import { useRef, useEffect, useContext } from 'react'; import AuthContext from './AuthContext'; function Profile() { const { token, logoutUser } = useContext(AuthContext); const isMounted = useRef(true); const [profile, setProfile] = useState({}); const getProfile = async () => { const response = await fetch("/profile/me/", { method: "GET", headers: { "Content-Type": "application/json", 'Authorization': `Token ${token}`, }, }); // 组件已卸载则直接终止后续逻辑 if (!isMounted.current) return; if (response.status === 401) { console.log("Should Log out"); logoutUser(); } else { const data = await response.json(); setProfile(data); } }; useEffect(() => { getProfile(); // 组件卸载时更新标记 return () => { isMounted.current = false; }; }, [token, logoutUser]); // 添加依赖,确保使用最新的token和logoutUser // 组件其他代码... }
2. 缓存logoutUser函数,保持引用稳定
在AuthContext中用useCallback包裹logoutUser,避免函数随provider渲染频繁重建:
import { useCallback } from 'react'; // AuthContext内部代码 const logoutUser = useCallback(() => { setToken(null); localStorage.removeItem('token'); navigate("/"); }, [navigate]); // useNavigate返回的函数是稳定的,可安全作为依赖 const contextData = { loginUser: loginUser, token: token, logoutUser: logoutUser, };
3. 取消未完成的fetch请求(可选)
用AbortController在组件卸载时取消未完成的请求,彻底避免无效回调:
const getProfile = async () => { const controller = new AbortController(); const signal = controller.signal; try { const response = await fetch("/profile/me/", { method: "GET", headers: { "Content-Type": "application/json", 'Authorization': `Token ${token}`, }, signal: signal, // 关联取消信号 }); if (response.status === 401) { console.log("Should Log out"); logoutUser(); } else { const data = await response.json(); setProfile(data); } } catch (error) { if (error.name === 'AbortError') { console.log("Fetch aborted"); } else { throw error; } } }; useEffect(() => { getProfile(); // 组件卸载时取消请求 return () => controller.abort(); }, [token, logoutUser]);
为什么导航栏登出按钮正常?
导航栏按钮是在组件正常挂载状态下同步调用logoutUser,不存在异步延迟导致的组件卸载或引用失效问题,因此可以正常执行。
内容的提问来源于stack exchange,提问作者Angelo
相关产品推荐
相关产品推荐

