React中Axios拦截器加Token遇页面刷新/无限循环问题求助
问题:Axios拦截器配合React Auth上下文导致无限刷新/循环问题
我需要为HTTP请求添加Access Token,采用的方案是使用useEffect监听auth对象变化,以此配置Axios请求拦截器(同时移除旧拦截器,避免使用过期的token),实现代码如下:
import { InternalAxiosRequestConfig } from "axios"; import { AuthContextProps, useAuth } from "react-oidc-context"; import { AXIOS_INSTANCE } from "./api_mutator"; import { AppSettings, useAppSettings } from "./appSettings"; import { createContext, useContext, useEffect, useState } from "react"; export const ApiContext = createContext<null | string>(null); export const ApiProvider = ({ children }) => { const appSettings = useAppSettings(); const auth = useAuth(); const [axiosInterceptorId, setAxiosInterceptorId] = useState<null | number>(null); useEffect(() => { console.log('ApiProvider.useEffect'); // 临时缓解刷新问题,但未解决根源 // 移除旧拦截器 if (axiosInterceptorId) { AXIOS_INSTANCE.interceptors.request.eject(axiosInterceptorId); setAxiosInterceptorId(null); } // 检查必要条件是否满足 const hasValidSettings = !!appSettings?.apiBaseUrl && !!appSettings?.truthPortalApi; const hasValidAuth = auth?.isAuthenticated && !!auth.user?.access_token; if (hasValidSettings && hasValidAuth) { // 保存最新的auth和settings引用,避免闭包陷阱 const latestAuth = auth; const latestAppSettings = appSettings; // 添加新拦截器 const interceptorId = AXIOS_INSTANCE.interceptors.request.use((config) => { return { ...config, baseURL: latestAppSettings.truthPortalApi, headers: { ...config.headers, Authorization: `Bearer ${latestAuth.user.access_token}`, }, }; }); setAxiosInterceptorId(interceptorId); } // 清理函数:组件卸载或依赖变化时移除拦截器 return () => { if (axiosInterceptorId) { AXIOS_INSTANCE.interceptors.request.eject(axiosInterceptorId); } }; }, [auth.isAuthenticated, auth.user?.access_token, appSettings?.apiBaseUrl, appSettings?.truthPortalApi]); // 直接从auth推导token,无需额外维护状态 const accessToken = auth?.isAuthenticated && auth.user?.access_token ? auth.user.access_token : null; return ( <ApiContext.Provider value={accessToken}> {children} </ApiContext.Provider> ); }; // Hook for consumers to know whether the API is available. export function useApi() { const at = useContext(ApiContext); return at != null && at !== ''; }
遇到的问题
- 上述代码会触发数百次页面刷新,添加
console.log('ApiProvider.useEffect');后该现象暂时停止; - 有人告知无需使用
useEffect,但未说明原因及替代方案,我认为必须用useEffect监听auth对象变化,否则无法获取更新后的token; - 若将拦截器相关逻辑移出
useEffect,直接放在组件函数中,页面会变白并触发无限循环错误。
原因分析
- 无限刷新根源:之前监听的是整个
auth和appSettings对象,而这两个对象可能在每次组件渲染时生成新引用(比如useAuth/useAppSettings内部状态更新时返回新对象),导致useEffect频繁触发,反复添加/移除拦截器,引发重渲染循环。添加console.log只是因控制台输出的性能开销轻微延迟了渲染,并未解决根本问题。 - 移出useEffect导致无限循环:组件函数每次渲染都会执行,若把拦截器逻辑直接放在这里,每次渲染都会添加新拦截器,而拦截器修改可能间接触发上下文/状态变化,导致组件再次渲染,形成无限循环。
- 关于是否需要useEffect:监听auth状态变化确实需要
useEffect——拦截器配置属于副作用操作(修改全局Axios实例),必须放在useEffect中避免每次渲染都执行。说不需要的人可能没考虑副作用时机,或有特定场景的替代方案,但你的场景下useEffect是合理选择,只是依赖项配置有误。
解决方案
1. 优化useEffect依赖项
不要监听整个auth或appSettings对象,而是监听具体影响拦截器配置的字段(比如auth.isAuthenticated、auth.user?.access_token、appSettings?.apiBaseUrl等)。只有当这些关键值变化时,才触发拦截器更新,避免不必要的useEffect执行。
2. 修复闭包陷阱
原代码中拦截器函数引用的是useEffect触发时的auth/appSettings快照,后续auth更新后,拦截器里的token还是旧值。通过在useEffect内部保存最新引用,确保拦截器能获取当前最新的token和配置。
3. 移除不必要的状态
原代码中的access_token状态可直接从auth对象推导,无需额外维护,减少状态更新带来的重渲染触发。
4. 完善清理函数
在useEffect的返回函数中明确移除拦截器,确保组件卸载或依赖变化时,旧拦截器被正确清理,避免内存泄漏或残留旧拦截器干扰请求。
内容的提问来源于stack exchange,提问作者Richard Barraclough
相关产品推荐
相关产品推荐

