React中如何基于Hook/Context配置AccessToken的Axios中间件
统一处理Axios请求头的可维护方案
核心问题
假设访问令牌通过Hook暴露,如何以可维护的方式配置Hook,统一处理Axios API请求中的令牌请求头?
当前已通过Context暴露访问令牌/权限并实现受保护路由,但希望在API调用环节利用该Hook设置Axios拦截器管理认证请求头,遇到的问题是API调用似乎需要嵌套在Hook中才能使用令牌,不清楚具体实现方式;同时使用react-query,期望全局配置但需在组件中访问Hook。
1. 独立封装Axios实例
不要直接修改全局axios.defaults,创建独立的Axios实例避免污染全局配置,提升可维护性:
// api/axiosInstance.ts import axios from 'axios'; const apiInstance = axios.create({ baseURL: process.env.REACT_APP_API_BASE_URL, // 替换为你的API基础地址 }); export default apiInstance;
2. 在AuthProvider中配置动态拦截器
利用AuthProvider的useEffect监听令牌变化,动态添加/更新请求拦截器,同时处理令牌失效的场景:
import React, { useContext, useState, useEffect, useMemo } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { useMutation } from '@tanstack/react-query'; import axios from 'axios'; import apiInstance from './api/axiosInstance'; // 导入封装的Axios实例 const AuthContext = React.createContext<any>(null); export const useAuth = () => { return React.useContext(AuthContext); }; const loginApiCall = (userName: string, password: string) => { if (!userName || !password) { return Promise.reject('缺少凭据') } return axios.post(`${auth_service}/oauth/token`, { username: userName, password: password }) }; const AuthProvider = ({ children }: any) => { const navigate = useNavigate(); const [token, setToken] = useState<string | null>(null); const location = useLocation(); // 监听令牌变化,动态配置Axios拦截器 useEffect(() => { // 请求拦截器:自动添加Authorization头 const requestInterceptor = apiInstance.interceptors.request.use( (config) => { if (token) { config.headers.Authorization = `Bearer ${token.access_token}`; } return config; }, (error) => Promise.reject(error) ); // 响应拦截器:处理401未授权,触发登出 const responseInterceptor = apiInstance.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401 && token) { handleLogout(); } return Promise.reject(error); } ); // 清理拦截器,避免内存泄漏 return () => { apiInstance.interceptors.request.eject(requestInterceptor); apiInstance.interceptors.response.eject(responseInterceptor); }; }, [token]); // 原有路由导航逻辑 useEffect(() => { if (location.pathname === '/login' && token) { navigate('/'); } else if (!token) { navigate('/login'); } }, [token, location.pathname, navigate]); const loginCall = useMutation((data: any) => loginApiCall(data.username, data.password), { onSuccess: (response) => { setToken(response.data); } }); const handleLogin = async (username: string, password: string) => { loginCall.mutate({ username, password }); }; const handleLogout = () => { setToken(null); // 可选:调用登出API使令牌失效 // apiInstance.post('/logout').catch(() => {}); navigate('/login'); }; const value = useMemo(() => ({ token, onLogin: handleLogin, onLogout: handleLogout, }), [token]); return ( <AuthContext.Provider value={value}> {children} </AuthContext.Provider> ); }; export default AuthProvider;
3. 在React Query中使用封装的Axios实例
所有API请求直接使用apiInstance,自动继承认证头,无需在每个query/mutation中手动配置:
// hooks/useUserProfile.ts import { useQuery } from '@tanstack/react-query'; import apiInstance from '../api/axiosInstance'; export const useUserProfile = () => { return useQuery({ queryKey: ['userProfile'], queryFn: () => apiInstance.get('/user/profile').then(res => res.data), }); };
方案优势
- 集中管理:所有认证相关的Axios配置统一在AuthProvider中,便于后续修改维护
- 自动同步:令牌更新/清除时,拦截器自动调整请求头,无需手动干预
- 避免冲突:独立Axios实例不会影响登录等无需认证的请求
- 减少重复代码:React Query的所有请求自动带上认证头,无需重复编写逻辑
内容的提问来源于stack exchange,提问作者iamaword
相关产品推荐
相关产品推荐

