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
相关产品推荐
相关产品推荐

