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

如何每隔t分钟自动刷新JWT token并修复useEffect中定时器异常问题

问题原因与修复方案

现有代码的核心问题

  • 闭包陷阱:useEffect依赖数组为空,只会在组件第一次挂载时执行,内部捕获的refreshToken、history都是初始值,如果挂载时refreshToken还未生成(比如登录状态还没初始化完成),后续refreshToken更新后useEffect也不会重新执行,定时器内部一直拿不到有效refreshToken,请求直接失败,看起来就像定时器没启动。
  • 定时器ID丢失:你没有在useEffect的清理函数中清除定时器,且refresh变量只存在于第一次useEffect的作用域内,后续如果组件重渲染、路由跳转后根组件实例变化,旧的定时器ID就会丢失,自然无法清除,导致定时器一直运行。
  • 异步请求堆叠:setInterval不管上一次请求是否完成,到点就发新请求,如果接口响应慢超过设置的间隔时间,会出现多个请求同时发送的异常情况,也会导致逻辑混乱。

修复后的实现代码

推荐用setTimeout递归调用替代setInterval,避免请求堆叠,同时用ref存储定时器ID保证能正常清除,补全依赖数组和清理逻辑:

import { useRef, useEffect } from 'react'

// 用ref存储定时器ID,跨渲染周期保持引用
const refreshTimerRef = useRef(null)

useEffect(() => {
  // 无有效refreshToken时不启动刷新逻辑
  if (!refreshToken) return

  async function getRefreshToken() {
    try {
      const response = await axios.post(
        "http://localhost:3001/refreshToken",
        { refreshToken },
        { withCredentials: true }
      )
      if (response.data.redirect) {
        // 先清除定时器再处理跳转
        if (refreshTimerRef.current) {
          clearTimeout(refreshTimerRef.current)
          refreshTimerRef.current = null
        }
        localStorage.clear()
        history.replace(response.data.redirect)
        return
      }
      // 请求成功后设置下一次刷新,此处替换为你需要的间隔时间
      refreshTimerRef.current = setTimeout(getRefreshToken, 4 * 60 * 1000)
    } catch (err) {
      // 接口报错也需要清除定时器,跳转登录
      if (refreshTimerRef.current) {
        clearTimeout(refreshTimerRef.current)
        refreshTimerRef.current = null
      }
      localStorage.clear()
      history.replace('/login')
    }
  }

  // 启动第一次刷新
  refreshTimerRef.current = setTimeout(getRefreshToken, 4 * 60 * 1000)

  // 组件卸载或依赖变化时自动清除定时器
  return () => {
    if (refreshTimerRef.current) {
      clearTimeout(refreshTimerRef.current)
      refreshTimerRef.current = null
    }
  }
}, [refreshToken, history]) // 把用到的外部变量加入依赖数组

更优的JWT刷新方案

定时轮询的性能消耗较高,推荐用被动刷新方案:

  • 接口响应触发:在axios响应拦截器中全局捕获401状态码,自动触发refreshToken逻辑,刷新成功后重发之前失败的请求,刷新失败再清空本地状态跳转到登录页。
  • 提前预判刷新:在请求拦截器中每次发送请求前,判断accessToken的过期时间,如果距离过期不足1分钟(可自定义阈值),就先执行刷新逻辑,拿到新的token后再发送当前请求,避免出现无效的401请求。
  • 全局状态托管:把token状态和刷新逻辑托管到全局状态管理工具(如Zustand、Redux)中,和组件生命周期解绑,避免组件挂载/卸载导致的定时器异常问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 17:09:00