You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 04:41:12