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

React自动刷新accessToken的逻辑建议及Hook调用问题求助

React自动刷新accessToken的合理实现方案

核心问题分析

你遇到的错误本质是:useAuth是React Hook,只能在函数组件或自定义Hook的顶层执行,绝对不能在普通JS类、Promise回调这类非React组件环境里调用,所以直接在API类的Promise里用肯定报错。

解决思路

把API类和React的AuthContext彻底解耦,不让API类直接依赖Hook,而是通过依赖注入的方式把认证相关能力(获取有效token、刷新token、登出)传给API类,同时把token刷新的核心逻辑封装在AuthContext里,处理并发刷新这类细节问题。

具体实现步骤

1. 改造API类,接收认证依赖

如果可以修改OpenAPI生成的类,或者封装一层,让API类初始化时接收一个authProvider对象,只负责调用传入的方法获取token,不自己处理认证逻辑:

// 封装后的API客户端示例
class ApiClient {
  constructor(authProvider) {
    this.authProvider = authProvider;
    // 保留OpenAPI生成的其他初始化逻辑(比如baseURL、拦截器等)
  }

  // 统一获取有效token的方法
  async getValidAccessToken() {
    return this.authProvider.getValidToken();
  }

  // 所有请求方法里调用这个方法拿token(比如OpenAPI生成的请求函数)
  async fetchUserProfile() {
    const token = await this.getValidAccessToken();
    // 用token发起请求,这里替换成OpenAPI生成的请求逻辑
    return fetch('/api/profile', {
      headers: { Authorization: `Bearer ${token}` }
    });
  }
}

2. 在AuthContext里封装完整的token刷新逻辑

把token过期判断、刷新、保存、登出、并发控制的逻辑都放在AuthContext的自定义Hook里:

// AuthContext.js
import { createContext, useContext, useState, useCallback } from 'react';

const AuthContext = createContext();

export function AuthProvider({ children }) {
  // 从localStorage加载初始认证状态
  const [authState, setAuthState] = useState(() => {
    const saved = localStorage.getItem('auth');
    return saved ? JSON.parse(saved) : {
      token: null,
      refreshToken: null,
      tokenExpiration: null
    };
  });

  // 并发刷新锁:防止多个请求同时触发token刷新
  let refreshPromise = null;

  // 保存认证信息到localStorage和状态
  const saveAuth = useCallback((authData) => {
    setAuthState(authData);
    localStorage.setItem('auth', JSON.stringify(authData));
  }, []);

  // 登出逻辑
  const logout = useCallback(() => {
    setAuthState({ token: null, refreshToken: null, tokenExpiration: null });
    localStorage.removeItem('auth');
    // 这里可以加跳转到登录页的逻辑
  }, []);

  // 获取有效token的核心方法
  const getValidToken = useCallback(async () => {
    const { token, refreshToken, tokenExpiration } = authState;

    // 没有token直接触发登出
    if (!token || !refreshToken) {
      logout();
      throw new Error('无有效认证令牌');
    }

    // 提前5分钟刷新token(避免刚好过期时请求失败)
    const expiresAt = Date.parse(tokenExpiration);
    const shouldRefresh = expiresAt - Date.now() < 5 * 60 * 1000;

    if (!shouldRefresh) return token;

    // 如果已有刷新请求在进行,直接复用这个Promise
    if (refreshPromise) return refreshPromise;

    try {
      // 调用你的刷新token接口(替换成OpenAPI生成的Auth.refresh方法)
      refreshPromise = this.Auth.refresh({
        accessToken: token,
        refreshToken: refreshToken
      }).then((response) => {
        const newAuthData = response.data;
        saveAuth(newAuthData);
        return newAuthData.token;
      });

      return await refreshPromise;
    } catch (err) {
      logout();
      throw new Error('令牌刷新失败');
    } finally {
      // 刷新完成后重置锁
      refreshPromise = null;
    }
  }, [authState, saveAuth, logout]);

  const contextValue = {
    ...authState,
    saveAuth,
    logout,
    getValidToken
  };

  return <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>;
}

export function useAuth() {
  return useContext(AuthContext);
}

3. 在组件中初始化API类,注入认证依赖

在根组件或需要使用API的父组件里,通过useAuth拿到getValidToken,传给API类实例:

// App.js
import { useAuth } from './AuthContext';
import { ApiClient } from './generated-api';

function App() {
  const { getValidToken } = useAuth();
  // 初始化API客户端,注入认证依赖
  const apiClient = new ApiClient({ getValidToken });

  // 可以把apiClient通过props或Context传给子组件使用
  return (
    <div className="App">
      {/* 子组件通过props/apiContext拿到apiClient发起请求 */}
    </div>
  );
}

关键细节

  • 并发请求控制:用refreshPromise锁确保同一时间只有一个刷新请求,避免重复调用refresh接口浪费资源
  • 提前刷新策略:不要等token完全过期再刷新,提前几分钟触发,避免请求刚好在token过期时失败
  • 职责分离:API类只负责HTTP请求,认证逻辑完全交给React Context,代码更清晰、易维护

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 04:42:54