如何避免React组件在Axios拦截器刷新Token前跳转登录页?
解决React中Axios刷新Token时组件提前跳转登录页的问题
问题根源
你的组件代码在拦截器完成Token刷新并重新发起请求前,就捕获到了第一次的401错误并执行了跳转逻辑。核心原因是原代码未区分"Token过期可通过Refresh Token刷新"和"真正未授权(Refresh Token也过期)"两种场景,组件直接处理了所有401响应,导致提前跳转。
解决方案
核心思路是将401错误的统一处理交给Axios拦截器,组件只处理最终的请求成功结果或非401类错误,避免提前触发跳转。
1. 优化Axios拦截器逻辑
修改拦截器,让它负责Token刷新、请求重试和登录跳转的完整流程,同时处理多请求并发401的场景:
// 全局变量:标记是否正在刷新Token,保存等待中的请求队列 let isRefreshing = false; let requestQueue = []; axios.interceptors.response.use( (response) => response, async (error) => { const { config, response } = error; // 仅处理Token过期导致的401,且未在刷新中 if (response?.status === 401 && !isRefreshing) { isRefreshing = true; try { // 发起Refresh Token请求 const refreshResp = await axios.post( "/api/auth/refresh", {}, { withCredentials: true } ); if (refreshResp.status === 200) { const newAccessToken = refreshResp.data.accessToken; // 更新全局Authorization请求头 axios.defaults.headers.common["Authorization"] = `Bearer ${newAccessToken}`; // 重新发起队列中所有等待的请求 requestQueue.forEach(({ resolve }) => resolve(axios(config))); requestQueue = []; // 重新发起当前请求,返回最终成功结果 return axios(config); } } catch (refreshErr) { // Refresh Token过期,直接跳转登录页 window.location.href = "/login"; // 若用React Router,可改用history.push('/login') // 清除无效的Token请求头 delete axios.defaults.headers.common["Authorization"]; // 拒绝队列中所有等待的请求 requestQueue.forEach(({ reject }) => reject(refreshErr)); requestQueue = []; return Promise.reject(refreshErr); } finally { // 重置刷新状态 isRefreshing = false; } } else if (response?.status === 401 && isRefreshing) { // 已有请求在刷新Token,将当前请求加入队列等待 return new Promise((resolve, reject) => { requestQueue.push({ resolve, reject, config }); }); } // 非401错误,直接抛给组件处理 return Promise.reject(error); } );
2. 调整组件与请求函数逻辑
组件不再处理401错误,仅关注请求成功或其他类型错误,同时添加挂载状态判断,避免组件卸载后更新状态导致的警告:
const [pages, setPages] = useState(); const [error, setError] = useState(); const navigate = useNavigate(); useEffect(() => { let isComponentMounted = true; async function fetchInfo() { try { const response = await getMyPages(); if (isComponentMounted) { setPages(response.data); } } catch (err) { if (isComponentMounted) { // 仅处理非401错误(401已被拦截器处理并跳转) setError(t("error.unexpected")); } } } fetchInfo(); // 组件卸载时标记为未挂载 return () => { isComponentMounted = false; }; }, [t, navigate]); // 修改getMyPages,移除内部try/catch,让错误向上抛给组件处理 export async function getMyPages() { return await axios.get(`/api/user/mypages`); }
3. (可选)实现React Router无刷新跳转
若不想用window.location.href导致页面刷新,可创建全局History实例:
// history.js import { createBrowserHistory } from 'history'; export const history = createBrowserHistory();
在App.js中替换默认Router:
import { Router } from 'react-router-dom'; import { history } from './history'; function App() { return ( <Router history={history}> {/* 你的路由组件 */} </Router> ); }
然后在拦截器中导入并使用:
import { history } from './history'; // 替换window.location.href为: history.push('/login');
内容的提问来源于stack exchange,提问作者roeld
相关产品推荐
相关产品推荐

