React(Vite+TS)中Axios拦截器调用Hook实现登出跳转的问题
解决方案:非组件环境调用Hook相关登出逻辑的几种实现方式
针对你在Vite+TS的React项目中,遇到的/tokens-renew返回401时无法在axios拦截器(非组件环境)调用含useDispatch/useNavigate的handleLogout问题,以下是几种实用的解决思路:
方案一:直接使用全局Store和Router实例
核心思路
Redux的Store实例本身是全局可导入的,React Router 6中也可以提前创建Router实例并导出,这样就能在拦截器里直接调用登出动作和路由跳转,完全不需要Hook。
代码示例
1. 导出Redux Store实例
// store.ts import { configureStore } from '@reduxjs/toolkit'; import authReducer from './features/auth/authSlice'; export const store = configureStore({ reducer: { auth: authReducer, }, }); export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch;
2. 导出React Router实例
// router.ts import { createBrowserRouter } from 'react-router-dom'; import LoginPage from './pages/LoginPage'; import Dashboard from './pages/Dashboard'; export const router = createBrowserRouter([ { path: '/login', element: <LoginPage /> }, { path: '/dashboard', element: <Dashboard /> }, ]);
3. 在拦截器中直接调用
// api/axios.ts import axios from 'axios'; import { store } from '../store'; import { router } from '../router'; import { logout } from '../features/auth/authSlice'; const api = axios.create({ baseURL: '/api' }); api.interceptors.response.use( (res) => res, async (error) => { const originalRequest = error.config; // 排除刷新接口本身的401 if (error.response?.status === 401 && originalRequest.url !== '/tokens-renew') { try { const refreshRes = await api.post('/tokens-renew'); // 更新请求头的token originalRequest.headers['Authorization'] = `Bearer ${refreshRes.data.accessToken}`; return api(originalRequest); } catch (refreshErr) { // 刷新失败,执行登出 store.dispatch(logout()); router.navigate('/login'); } } return Promise.reject(error); } ); export default api;
方案二:组件层传递登出回调到拦截器
核心思路
在根组件(如App.tsx)中获取dispatch和navigate,然后将封装好的登出函数传递给拦截器,让拦截器只负责触发回调,不关心具体实现。
代码示例
1. 拦截器中预留回调入口
// api/axios.ts import axios from 'axios'; const api = axios.create({ baseURL: '/api' }); let logoutCallback: (() => void) | undefined; // 暴露设置回调的方法 export const setLogoutCallback = (callback: () => void) => { logoutCallback = callback; }; api.interceptors.response.use( (res) => res, async (error) => { const originalRequest = error.config; if (error.response?.status === 401 && originalRequest.url !== '/tokens-renew') { try { const refreshRes = await api.post('/tokens-renew'); originalRequest.headers['Authorization'] = `Bearer ${refreshRes.data.accessToken}`; return api(originalRequest); } catch (refreshErr) { // 调用组件层传递的登出回调 logoutCallback?.(); } } return Promise.reject(error); } ); export default api;
2. 根组件中设置回调
// App.tsx import { useEffect } from 'react'; import { useDispatch, useNavigate } from 'react-router-dom'; import { setLogoutCallback } from './api/axios'; import { logout } from './features/auth/authSlice'; function App() { const dispatch = useDispatch(); const navigate = useNavigate(); useEffect(() => { setLogoutCallback(() => { dispatch(logout()); navigate('/login'); }); }, [dispatch, navigate]); return ( <div className="app-container"> {/* 页面内容 */} </div> ); } export default App;
方案三:使用事件总线解耦逻辑
核心思路
用事件总线(如mitt库)在拦截器中触发登出事件,在组件中监听该事件并执行含Hook的登出逻辑,完全解耦拦截器和组件的依赖。
代码示例
1. 创建事件总线
// utils/eventBus.ts import mitt from 'mitt'; // 定义事件类型 type Events = { triggerLogout: void; }; export const eventBus = mitt<Events>();
2. 拦截器中触发事件
// api/axios.ts import axios from 'axios'; import { eventBus } from '../utils/eventBus'; const api = axios.create({ baseURL: '/api' }); api.interceptors.response.use( (res) => res, async (error) => { const originalRequest = error.config; if (error.response?.status === 401 && originalRequest.url !== '/tokens-renew') { try { const refreshRes = await api.post('/tokens-renew'); originalRequest.headers['Authorization'] = `Bearer ${refreshRes.data.accessToken}`; return api(originalRequest); } catch (refreshErr) { // 触发登出事件 eventBus.emit('triggerLogout'); } } return Promise.reject(error); } ); export default api;
3. 组件中监听事件
// App.tsx import { useEffect } from 'react'; import { useDispatch, useNavigate } from 'react-router-dom'; import { eventBus } from './utils/eventBus'; import { logout } from './features/auth/authSlice'; function App() { const dispatch = useDispatch(); const navigate = useNavigate(); useEffect(() => { const handleLogout = () => { dispatch(logout()); navigate('/login'); }; // 监听登出事件 eventBus.on('triggerLogout', handleLogout); // 组件卸载时移除监听 return () => eventBus.off('triggerLogout', handleLogout); }, [dispatch, navigate]); return ( <div className="app-container"> {/* 页面内容 */} </div> ); } export default App;
内容的提问来源于stack exchange,提问作者Rohit
相关产品推荐
相关产品推荐

