React-router-dom v6 useNavigate跳转时Axios请求拦截器失效问题
问题分析
你遇到的核心问题是:Axios请求拦截器已在组件的useEffect中执行注册,但跳转页面后的请求未触发拦截器,导致Authorization头缺失。根源在于拦截器注册时机与请求发起时机不匹配,以及组件挂载/卸载过程中拦截器被重复操作:
- 拦截器绑定在React组件的
useEffect中,若页面请求在拦截器完成注册前发起(比如组件重新渲染时的异步状态更新延迟),就会出现拦截器未触发的情况。 - 每次组件挂载都会注册新拦截器,卸载时又会移除旧拦截器,若路由跳转导致组件卸载再挂载,请求可能刚好落在拦截器被移除后、新拦截器注册前的窗口。
解决方案
方案1:全局注册拦截器(推荐)
将拦截器从React组件中移出,改为模块加载时全局注册,确保拦截器始终生效。通过全局变量传递navigate函数实现客户端路由跳转:
- 修改Axios拦截器文件:
import axios from 'axios'; import KeycloakService from '../services/KeycloakService'; // 全局存储navigate函数 let navigateFn: ((to: string, options?: any) => void) | undefined; const instance = axios.create(); // 请求拦截器 const reqInterceptor = (request: any) => { if (KeycloakService.isLoggedIn()) { const token = KeycloakService.getToken(); request.headers['Authorization'] = `Bearer ${token}`; } return request; }; // 响应拦截器 const resInterceptor = (response: any) => { return response; }; // 错误拦截器 const errInterceptor = (error: any) => { if (error.response?.status === 401) { navigateFn?.('/home'); } return Promise.reject(error); }; // 模块加载时注册拦截器(仅执行一次) instance.interceptors.request.use(reqInterceptor, errInterceptor); instance.interceptors.response.use(resInterceptor, errInterceptor); // 暴露设置navigate的方法 export const setNavigateFn = (fn: (to: string, options?: any) => void) => { navigateFn = fn; }; export default instance;
- 在App.tsx中初始化
navigate函数:
import { useEffect, useNavigate } from 'react-router-dom'; import { setNavigateFn } from './hooks/AxiosInterceptor'; function App() { const navigate = useNavigate(); useEffect(() => { setNavigateFn(navigate); }, [navigate]); return ( <BrowserRouter> <AuthProvider> <Routes> {/* 你的路由配置 */} </Routes> </AuthProvider> </BrowserRouter> ); } export default App;
- 删除原有的
AxiosInterceptor组件,拦截器已全局生效,无需再通过组件控制渲染时机。
方案2:保留组件,确保拦截器仅注册一次
若需保留AxiosInterceptor组件,通过全局标记避免重复注册,并移除卸载时的拦截器移除逻辑:
import axios from 'axios'; import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import KeycloakService from '../services/KeycloakService'; const instance = axios.create(); // 全局标记:是否已注册拦截器 let isInterceptorRegistered = false; const AxiosInterceptor = ({ children }: any) => { const [isSet, setIsSet] = useState(false); const navigate = useNavigate(); useEffect(() => { if (!isInterceptorRegistered) { const reqInterceptor = (request: any) => { if (KeycloakService.isLoggedIn()) { const token = KeycloakService.getToken(); request.headers['Authorization'] = `Bearer ${token}`; } return request; }; const resInterceptor = (response: any) => { return response; }; const errInterceptor = (error: any) => { if (error.response?.status === 401) { navigate('/home'); } return Promise.reject(error); }; instance.interceptors.request.use(reqInterceptor, errInterceptor); instance.interceptors.response.use(resInterceptor, errInterceptor); isInterceptorRegistered = true; } setIsSet(true); // 移除卸载时的拦截器移除逻辑,避免拦截器被意外销毁 return () => {}; }, [navigate]); return isSet && children; }; export default instance; export { AxiosInterceptor };
验证要点
- 确保所有Axios请求都使用从拦截器文件导出的
instance,而非直接导入axios包。 - 在拦截器中添加
console.log调试,确认KeycloakService.isLoggedIn()和getToken()能正确返回状态和令牌。 - 跳转页面后,查看网络请求的请求头是否包含
Authorization字段。
内容的提问来源于stack exchange,提问作者era
相关产品推荐
相关产品推荐

