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

React Axios响应拦截器中Refresh Token过期如何触发自定义弹窗后跳转登录

React Context环境下axios拦截器触发自定义会话过期弹窗方案

核心思路

axios拦截器属于普通JS模块,无法直接调用React组件内的Context方法,所以需要搭建一个跨模块的通信通道,让拦截器可以发送会话过期的信号,Context层接收信号后控制自定义弹窗的显示。

具体实现步骤

1. 改造Auth Context Provider

首先在你的自定义鉴权Context中新增弹窗控制逻辑,同时内置自定义弹窗组件:

import { createContext, useContext, useState, useEffect } from 'react'
import { useHistory } from 'react-router-dom'
import CustomModal from './你的自定义弹窗组件路径'

const AuthContext = createContext()

export const AuthProvider = ({ children }) => {
  const history = useHistory()
  const [showExpiredModal, setShowExpiredModal] = useState(false)
  // 加锁避免重复触发弹窗
  const [modalLocked, setModalLocked] = useState(false)

  // 弹窗确认后的回调
  const handleConfirm = () => {
    setShowExpiredModal(false)
    setModalLocked(false)
    // 这里执行你原来的清空存储+跳转逻辑
    removeLocalStorageData()
    history.replace('/login')
  }

  // 对外暴露的触发弹窗方法
  const showSessionExpiredModal = () => {
    if (modalLocked) return
    setShowExpiredModal(true)
    setModalLocked(true)
  }

  // 方案A:用全局事件总线接收信号的逻辑,后面会讲
  useEffect(() => {
    const handleExpired = () => showSessionExpiredModal()
    window.eventBus.on('sessionExpired', handleExpired)
    return () => window.eventBus.off('sessionExpired', handleExpired)
  }, [])

  // 方案B:挂载到全局window的简化方案,不需要事件总线就用这个
  // useEffect(() => {
  //   window.$auth = { showSessionExpiredModal }
  //   return () => delete window.$auth
  // }, [])

  return (
    <AuthContext.Provider value={{ showSessionExpiredModal /* 其他你原来的context值 */ }}>
      {children}
      {/* 自定义弹窗直接放在Provider里,全局复用 */}
      <CustomModal
        visible={showExpiredModal}
        title="提示"
        content="会话已过期,请重新登录"
        onConfirm={handleConfirm}
        maskClosable={false} // 禁止点击蒙层关闭,必须点确认
      />
    </AuthContext.Provider>
  )
}

export const useAuth = () => useContext(AuthContext)

2. 实现跨模块通信通道

方案A:极简事件总线(推荐,无全局污染)

新建eventBus.js文件,实现基础的订阅发布能力:

class EventBus {
  constructor() {
    this.events = {}
  }
  on(eventName, callback) {
    if (!this.events[eventName]) this.events[eventName] = []
    this.events[eventName].push(callback)
  }
  emit(eventName, ...args) {
    if (this.events[eventName]) {
      this.events[eventName].forEach(cb => cb(...args))
    }
  }
  off(eventName, callback) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(cb => cb !== callback)
    }
  }
}

window.eventBus = new EventBus()

在项目入口文件index.js里引入这个文件,确保全局初始化。

方案B:全局挂载(适合小型项目,代码量最少)

不需要额外新建文件,直接用上面Context代码里注释的挂载到window的逻辑即可。

3. 修改原有axios拦截器逻辑

把你原来doRefreshToken函数里直接清空存储和刷新页面的逻辑,替换成发通信信号即可:

export const doRefreshToken = async (err, API) => {
  const originalConfig = err.config;

  if (err.response) {
    if (err.response.status === 401 && !originalConfig._retry) {
      originalConfig._retry = true;
      try {
        const rs = await refreshTokenAPI();
          const { token, refreshToken } = rs.data;
          setToken(token);
          setRefreshToken(refreshToken);
          return API(originalConfig);
      } catch (_error) {
        // 删掉原来的removeLocalStorageData和window.location.reload
        // 触发会话过期信号
        window.eventBus.emit('sessionExpired')
        // 如果用方案B全局挂载,就写window.$auth?.showSessionExpiredModal()
        return Promise.reject(_error);
      }
    }

    if (err.response.status === 403 && err.response.data && originalConfig.url === '/refresh-token') {
      // 同样删掉原来的清空和刷新逻辑,触发信号
      window.eventBus.emit('sessionExpired')
      // 如果用方案B就写window.$auth?.showSessionExpiredModal()
    }

    if (err.response.status === 403 && err.response.data) {
      return Promise.reject(err.response.data);
    }
    
  }
}

注意事项

  • 弹窗要设置maskClosable=false且不显示取消按钮,避免用户跳过登录继续操作
  • 加弹窗锁是为了避免多个请求同时过期时重复触发多次弹窗
  • 所有清空存储、路由跳转的逻辑都放在弹窗的确认回调里执行,确保用户看到提示后才跳转

内容的提问来源于stack exchange,提问作者Smith Dwayne

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 13:54:03