React中useState存储Axios实例为何变为Promise?如何解决?
原因分析
你遇到的问题核心在于Axios默认导出的实例本身是一个可调用的函数(typeof api === 'function'),而React的useState有特殊规则:如果传入的初始值是函数类型,React会自动执行这个函数,并将其返回值作为初始state。
当你执行const [axios, setAxios] = useState(api)时,React把api(Axios实例)当成了初始化函数,调用api()会返回一个Promise(调用Axios实例本身等同于发起请求,返回Promise),所以state中的axios最终变成了Promise,而非Axios实例本身。
基础问题解决方法
要避免这个问题,你需要用函数式初始化的方式传入初始值,让React执行你提供的箭头函数,返回Axios实例:
import {createContext, useEffect, useState} from "react"; import api from "axios"; export const AxiosContext = createContext({ axios: null }) export const AxiosContextProvider = ({ children }) => { // 用箭头函数包裹Axios实例,避免被React当作初始化函数执行 const [axios, setAxios] = useState(() => api); console.log(api); // 正常Axios实例 console.log(axios); // 现在也是Axios实例 return( <AxiosContext.Provider value={{ axios }}> {children} </AxiosContext.Provider> ) }
针对授权令牌更新的场景解决方案
结合你需要在登录后动态更新Axios实例请求头的场景,更合理的方案不是替换整个Axios实例,而是直接修改现有实例的默认请求头,或使用请求拦截器动态注入令牌:
方案1:直接修改Axios实例的默认请求头
在用户登录成功后,直接更新现有Axios实例的默认请求头:
// 登录成功后调用 const updateAuthToken = (token) => { api.defaults.headers.common['Authorization'] = `Bearer ${token}`; };
这种方式无需修改state,所有组件使用的都是同一个Axios实例,修改后立即生效。
方案2:使用请求拦截器动态注入令牌
如果令牌可能过期或需实时获取(比如从localStorage读取),推荐使用拦截器:
// 在Axios实例创建后添加拦截器 api.interceptors.request.use( (config) => { const token = localStorage.getItem('authToken'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => { return Promise.reject(error); } );
登录成功后只需将令牌存入localStorage,拦截器会自动在每次请求时注入令牌,无需手动更新实例。
方案3:结合Context和状态管理
如果需要在令牌变化时通知组件重新渲染,可以将令牌存在Context的state中,拦截器从Context读取令牌:
// 在AxiosContextProvider中添加令牌状态 export const AxiosContextProvider = ({ children }) => { const [authToken, setAuthToken] = useState(localStorage.getItem('authToken')); // 登录成功后调用setAuthToken更新令牌 const login = (token) => { localStorage.setItem('authToken', token); setAuthToken(token); }; // 创建Axios实例(令牌变化时重新生成) const [apiInstance] = useState(() => { const instance = api.create(); instance.interceptors.request.use((config) => { if (authToken) { config.headers.Authorization = `Bearer ${authToken}`; } return config; }); return instance; }, [authToken]); return( <AxiosContext.Provider value={{ axios: apiInstance, login }}> {children} </AxiosContext.Provider> ) }
内容的提问来源于stack exchange,提问作者Vinicius Cavalcante Santos

