StrictMode下useLayoutEffect中axios重复调用API的问题咨询
React StrictMode下RefreshToken请求重复触发的问题分析与解决
问题根源
你的情况是React StrictMode开发环境下的正常特性暴露了代码中的潜在bug,而非StrictMode本身的问题:
- StrictMode在开发环境会双重执行组件的副作用(包括
useLayoutEffect)、状态更新和部分函数调用,目的是检测那些在重复执行时会异常的代码。 - 你的代码存在两个核心问题:
useLayoutEffect没有设置依赖数组,导致每次组件渲染都会重复添加axios响应拦截器,即使有清理逻辑,也会在StrictMode的双重执行下出现拦截器多次触发的情况。- 没有处理并发的RefreshToken请求:当第一次401触发刷新请求时,第二次重复的401请求会在浏览器尚未更新新的HttpOnly Cookie时发起,携带旧的RefreshToken,而此时数据库中的旧Token已经被第一次请求更新,导致第二次请求失败。
解决方案
1. 给useLayoutEffect添加空依赖数组
确保拦截器只在组件挂载时注册一次,即使StrictMode双重执行,最终也只会保留一个有效拦截器:
useLayoutEffect(() => { const refreshInterceptor = axios.interceptors.response.use( (response) => response, async (error) => { // 原有拦截器逻辑 } ); return () => { axios.interceptors.response.eject(refreshInterceptor); }; }, []); // 添加空依赖数组,仅在组件挂载/卸载时执行
2. 添加并发刷新锁与请求队列
防止同一时间多次触发RefreshToken请求,同时缓存等待刷新的请求,在刷新完成后统一重试:
首先在AuthContext中新增状态:
const [isRefreshing, setIsRefreshing] = useState(false); const [failedRequestsQueue, setFailedRequestsQueue] = useState([]);
然后修改拦截器逻辑:
useLayoutEffect(() => { const refreshInterceptor = axios.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; // 排除RefreshToken自身请求,且标记过_retry的请求不再处理 if ( error.response && error.response.status === 401 && originalRequest.url !== "api/v1/refreshToken" && !originalRequest._retry ) { // 如果正在刷新,将请求加入队列等待 if (isRefreshing) { return new Promise((resolve, reject) => { setFailedRequestsQueue(prev => [...prev, { resolve, reject, request: originalRequest }]); }); } setIsRefreshing(true); try { const response = await axios.post("api/v1/refreshToken", {}, { withCredentials: true }); setToken(response.data.token); // 更新原请求的Authorization头 originalRequest.headers.Authorization = `Bearer ${response.data.token}`; originalRequest._retry = true; // 重试队列中所有等待的请求 setFailedRequestsQueue(queue => { queue.forEach(({ resolve, request }) => { request.headers.Authorization = `Bearer ${response.data.token}`; resolve(axios(request)); }); return []; }); return axios(originalRequest); } catch { setToken(null); setUser(null); // 刷新失败时拒绝队列中所有请求 setFailedRequestsQueue(queue => { queue.forEach(({ reject }) => reject(error)); return []; }); } finally { setIsRefreshing(false); } } return Promise.reject(error); } ); return () => { axios.interceptors.response.eject(refreshInterceptor); }; }, [isRefreshing, setToken, setUser]); // 添加相关依赖
3. 服务端兜底优化(可选)
可以在服务端实现RefreshToken的短窗口有效性:在更新RefreshToken后,保留旧Token在短时间内有效(比如5秒),避免因为前端重复请求导致的验证失败,增强系统的鲁棒性。
总结
StrictMode的双重执行是开发环境的正常检测机制,它帮你发现了代码中拦截器重复注册和并发请求处理缺失的问题。通过上述修复,你的代码可以在保留StrictMode的情况下正常工作,同时也提升了生产环境下的稳定性。
内容的提问来源于stack exchange,提问作者Quikler
相关产品推荐
相关产品推荐

