react-router-dom v6.3 axios拦截器history.push跳转无效问题
react-router-dom v6.3.0 全局history.push 触发路由跳转失效修复
问题根因
- history库版本不匹配:react-router-dom@6.3.0 内部依赖的是history v5版本的事件监听、路由更新逻辑,如果你项目中自行安装的是history v6版本,自己通过
createBrowserHistory创建的实例API和Router内部逻辑不兼容,会出现执行history.push时无路由响应的问题,和路由无关的日志打印、localStorage清理逻辑不受影响,可正常执行。 - 循环引用导致实例异常:你将history实例定义在
App.tsx组件文件中导出,在axios拦截器文件中引入App模块,很容易在模块初始化阶段拿到未完成初始化的history实例,调用方法自然不会生效。 - 现有拦截器逻辑存在隐性bug:
res变量定义在try块作用域内,catch块外直接访问会抛出引用错误;refresh失败后未中断流程,仍会重试原请求,可能触发无效请求或死循环。
修复步骤
- 安装匹配版本的history依赖
# npm npm install history@5 # yarn yarn add history@5
- 抽离全局history实例为独立文件,避免循环引用
新建src/router/history.ts文件,单独维护history实例:
import { createBrowserHistory } from 'history'; export const history = createBrowserHistory();
- 修正App.tsx的引入逻辑
import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { history } from './router/history'; import Views from './Views'; const queryClient = new QueryClient(); function App() { return ( <QueryClientProvider client={queryClient}> <HistoryRouter history={history}> <Views /> </HistoryRouter> </QueryClientProvider> ); } export default App;
- 修正axios拦截器逻辑,补全错误中断、防死循环处理
import axios from 'axios'; import { history } from './router/history'; // 替换为你项目里的实际baseUrl、refresh实例配置 const baseUrl = import.meta.env.VITE_API_BASE_URL; const axiosInstance = axios.create({ baseURL: baseUrl }); const axiosInstanceRefresh = axios.create({ baseURL: baseUrl }); const refreshAccessToken = async () => { try { const res = await axiosInstanceRefresh.post( 'auth/refresh', null, { headers: { 'Authorization': `Bearer ${localStorage.getItem('refreshToken')}` } } ) // 存储新的token对 const { accessToken, refreshToken } = res.data; localStorage.setItem('accessToken', accessToken); localStorage.setItem('refreshToken', refreshToken); return accessToken; } catch (err) { console.log('refresh token已失效,跳转至首页'); localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); // 用replace代替push,避免用户点返回回到受保护路由 history.replace('/'); // 抛出错误中断后续重试流程 return Promise.reject(err); } } axiosInstance.interceptors.request.use(async request => { request.headers.Authorization = `Bearer ${localStorage.getItem('accessToken')}`; return request; }, error => Promise.reject(error)); axiosInstance.interceptors.response.use(response => response, async error => { const originalRequest = error.config; // 加_retry标记防止刷新token死循环 if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; const newAccessToken = await refreshAccessToken(); originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; return axiosInstance(originalRequest); } return Promise.reject(error); }); export default axiosInstance;
额外优化建议
- 鉴权失效跳转优先用
history.replace而非push,避免用户通过浏览器返回键重新进入触发401的受保护页面,造成重复跳转。 - 拦截器中一定要加重试标记,否则401场景下会无限触发token刷新逻辑,造成接口死循环调用。
- 全局路由相关的工具实例(history、路由常量等)尽量抽离到独立的router目录文件维护,不要在组件文件中导出给非组件模块使用,避免循环引用问题。
内容的提问来源于stack exchange,提问作者Dmitriy_kzn
相关产品推荐
相关产品推荐

