Axios拦截器导航触发循环:跳转登录后返回需多次点击返回键
Axios拦截器跳转登录页后返回键需多次点击的问题解决
我用Axios拦截器处理请求时校验用户登录状态,未登录就跳转到对应登录页,但现在有个问题:用户被跳转到登录页后,得点击返回键3-4次才能回到之前的页面,明明每次操作只发了一次请求。
当前拦截器代码
const usePrivateAxios = () => { const navigate = useNavigate(); const location = useLocation(); const currentPath = location.pathname; useEffect(() => { const requestInterceptor = privateAxios.interceptors.request.use( (config) => { const accessToken = localStorage.getItem("accessToken"); const user = JSON.parse(sessionStorage.getItem("profile")); if (!accessToken || !user) { if (currentPath.includes("/admin")) { navigate("/admin/login"); } else if (currentPath.includes("/manager")) { navigate("/manager/login"); } else { navigate("/login"); } toast.error( <p className="pr-2">Vui lòng đăng nhập trước!</p>, toastOptions, ); } else { config.headers.Authorization = `Bearer ${accessToken}`; sessionStorage.removeItem("entryMessage"); } return config; }, (error) => Promise.reject(error), ); const responseInterceptor = privateAxios.interceptors.response.use( (response) => { const user = JSON.parse(sessionStorage.getItem("profile")); if (response.data.status === 401 || response.data.status === 403) { if (response.data.status === 401) { toast.error( <p className="pr-2">Vui lòng đăng nhập trước!</p>, toastOptions, ); } else if (response.data.status === 403) { toast.error( <p className="pr-2">Tài khoản không có quyền truy cập!</p>, toastOptions, ); } if (user && user.role && user.role.roleName === "ROLE_ADMIN") { navigate("/admin/login"); } else if ( user && user.role && user.role.roleName === "ROLE_MANAGER" ) { navigate("/manager/login"); } else { navigate("/login"); } } return response; }, (error) => { return Promise.reject(error); }, ); return () => { privateAxios.interceptors.request.eject(requestInterceptor); privateAxios.interceptors.response.eject(responseInterceptor); }; }, [navigate]); return privateAxios; };
补充:拦截器处理点赞请求示例
// API export const likePost = async (postId, config) => { try { const response = await privateAxios.post(`/posts/${postId}/like`, config); return response.data; } catch (error) { throw error; } }; // Func const handlePostLike = async () => { if (post && post.my) { toast.error(<p className="pr-2">Bạn không thể thích/bỏ thích bài đăng của mình!</p>, toastOptions); } else { try { const response = await likePost(postId); if (response.status === 200) { getPostDetail(); } catch (error) { navigate("/error-500"); } } };
问题原因
- 拦截器重复注册:
usePrivateAxios每次调用或组件重渲染时,都会注册新的拦截器,旧拦截器未被正确清理,导致多次触发跳转,路由栈累积多条登录页记录。 - 跳转未终止请求:请求拦截器判断未登录后,未终止请求流程,后续逻辑可能重复触发跳转。
- 路由跳转方式错误:用默认的
navigate会添加新路由记录,而非替换当前记录,导致路由栈变长。
修复方案
const usePrivateAxios = () => { const navigate = useNavigate(); const location = useLocation(); const interceptorsRegistered = useRef(false); // 标记拦截器是否已注册 useEffect(() => { if (interceptorsRegistered.current) return; const requestInterceptor = privateAxios.interceptors.request.use( (config) => { const accessToken = localStorage.getItem("accessToken"); const user = JSON.parse(sessionStorage.getItem("profile")); const currentPath = location.pathname; if (!accessToken || !user) { // 跳转时替换当前路由,避免路由栈累积 const targetLoginPath = currentPath.includes("/admin") ? "/admin/login" : currentPath.includes("/manager") ? "/manager/login" : "/login"; navigate(targetLoginPath, { replace: true }); toast.error( <p className="pr-2">Vui lòng đăng nhập trước!</p>, toastOptions, ); return Promise.reject(new Error("未登录,已跳转")); // 终止请求 } else { config.headers.Authorization = `Bearer ${accessToken}`; sessionStorage.removeItem("entryMessage"); } return config; }, (error) => Promise.reject(error), ); const responseInterceptor = privateAxios.interceptors.response.use( (response) => { const user = JSON.parse(sessionStorage.getItem("profile")); if (response.data.status === 401 || response.data.status === 403) { const targetLoginPath = user?.role?.roleName === "ROLE_ADMIN" ? "/admin/login" : user?.role?.roleName === "ROLE_MANAGER" ? "/manager/login" : "/login"; navigate(targetLoginPath, { replace: true }); toast.error( response.data.status === 401 ? <p className="pr-2">Vui lòng đăng nhập trước!</p> : <p className="pr-2">Tài khoản không có quyền truy cập!</p>, toastOptions, ); return Promise.reject(new Error(response.data.status === 401 ? "未登录" : "无权限")); } return response; }, (error) => { // 处理后端直接返回HTTP 401/403的情况 if (error.response?.status === 401 || error.response?.status === 403) { const user = JSON.parse(sessionStorage.getItem("profile")); const targetLoginPath = user?.role?.roleName === "ROLE_ADMIN" ? "/admin/login" : user?.role?.roleName === "ROLE_MANAGER" ? "/manager/login" : "/login"; navigate(targetLoginPath, { replace: true }); toast.error( error.response.status === 401 ? <p className="pr-2">Vui lòng đăng nhập trước!</p> : <p className="pr-2">Tài khoản không có quyền truy cập!</p>, toastOptions, ); } return Promise.reject(error); }, ); interceptorsRegistered.current = true; return () => { privateAxios.interceptors.request.eject(requestInterceptor); privateAxios.interceptors.response.eject(responseInterceptor); interceptorsRegistered.current = false; // 卸载时重置标记 }; }, [navigate, location.pathname]); return privateAxios; };
核心修复点
- 拦截器去重:用
useRef标记拦截器注册状态,避免重复添加拦截器导致多次跳转。 - 替换路由跳转:
navigate时添加replace: true,直接替换当前路由记录,不再新增条目。 - 终止未登录请求:判断未登录后返回
Promise.reject(),阻止请求继续执行,避免触发后续逻辑。 - 完善错误处理:同时覆盖后端返回
response.data.status和HTTP状态码两种401/403场景。
内容的提问来源于stack exchange,提问作者Văn Thuận Nguyễn
相关产品推荐
相关产品推荐

