在前端开发中,http请求层的封装可以极大提升代码的复用性和可维护性,本文将完整的用axios封装接口请求,配置请求与响应拦截器,封装统一的请求方法全过程。
封装的目的和思路
在项目直接用axios发送请求当然没问题,但是如果不做封装,每个请求都需要手动处理token,错误提示等逻辑,容易重复,缺乏统一的loading处理逻辑,不同组件可以用不一致的方式调用axios增加维护成本。业务错误,比如登录失败权限不足不集中处理用户体验差。
所以我们统一封装是为了实现这些目标。
1.封装Axios实例+设置baseURL和超时。
2.添加请求拦截器:自动加token显示Loading
3.添加响应拦截器 统一处理错误隐藏loading
4 暴露统一的get/post等请求方法
5.支持类型推导(泛型)
step1:安装Axios
npm install axios
step2:创建Axios实例并且封装基础配置
// api/request.ts
import axios, { AxiosError } from 'axios'
import { message } from 'antd'
import { showLoading, hideLoading } from './loading'const instance = axios.create({baseURL: '/api', // 接口统一前缀timeout: 8000, // 超时时间timeoutErrorMessage: '请求超时,请稍后再试',withCredentials: false, // 是否携带 Cookie
})
为什么要用 axios.create?
可以创建多个实例,分别管理不同的 baseURL(如主站与后台接口)。
不影响全局 axios,互不干扰。
step3:请求拦截器
instance.interceptors.request.use((config) => {showLoading()const token = localStorage.getItem('token')if (token) {config.headers.Authorization = 'Token::' + token}return { ...config }},(error: AxiosError) => {return Promise.reject(error)}
)
请求拦截器做了什么?
1.请求前显示 loading(配合 Ant Design 的 Spin)
2.自动从 localStorage 中读取 token,统一加入到请求头中
3.你也可以添加其他自定义 headers,例如用户 ID、语言偏好等
step4:响应拦截器--统一 错误处理以及成功返回data
instance.interceptors.response.use((response) => {hideLoading()const data = response.dataif (data.code === 500001) {// 登录失效message.error(data.msg || '身份已过期,请重新登录')localStorage.removeItem('token')return Promise.reject(data)} else if (data.code !== 0) {// 业务错误message.error(data.msg || '请求错误,请稍后重试')return Promise.reject(data)}return data.data // 请求成功,返回业务数据},(error: AxiosError) => {hideLoading()message.error(error.message || '服务器异常,请稍后重试')return Promise.reject(error)}
)
拦截器可能有人不理解概念。这里把概念放在这里。
axios.interceptors.request.use 是 Axios 提供的 API,用来设置请求发出前的统一处理逻辑。
它接收两个回调函数,第一个是处理请求体 config 的,我们通常在这里加上 token、显示 loading,处理完之后必须返回 config 否则请求会被中断。
第二个是请求配置阶段发生错误时的处理函数,比如拦截器中抛出异常,这个函数里我们一般把错误通过 Promise.reject 抛出去,供 .catch 捕获使用。
Axios 的响应拦截器也接收两个回调函数,第一个是响应成功时调用,它的参数是 response,从中我们可以提取 response.data,然后判断自定义的 code 字段,来决定是 token 过期、业务出错还是成功。如果 token 过期或失败,会弹出错误提示,并通过 Promise.reject 抛出错误。
第二个回调函数是响应失败(如网络错误、404、500)时触发的,我们可以统一显示“服务器异常”等提示,也用 Promise.reject 抛出错误,供组件用 .catch() 捕获。
后端返回的 data 结构通常如下:
{
"code": 0,
"msg": "成功",
"data": { "userInfo": { ... } }
}
然后我们就可以明显看到我们请求拦截器实际上就是config这个请求体在发送之前我们去加一些逻辑去处理我们的config请求体。比如头部加上Authorization属性值为token把我们的token加上去。还有第二个回调函数,就是把错误作为Promise实例的reject(error)扔出去,这样我们组件可以用.catch捕捉。
响应拦截器同理,response就是我们的响应体。我们响应体在到组件接收之前我们对响应体做一些处理,比如我们不需要响应体,我们只是想要里面的data.data那么成功就返回data.data如果token过期了久调用message这是个组件然后.error显示错误比如token过期了。总之只要有响应体一定会返回data给到我们的组件。如果响应拦截的时候就发现报错了那么就reject扔出error组件.catch捕获。
step5:封装统一请求方法(泛型)
export default {get<T>(url: string, params?: object): Promise<T> {return instance.get(url, { params })},post<T>(url: string, params?: object): Promise<T> {return instance.post(url, params)},// 后续可添加 put、delete 等
}
为什么要用泛型 <T>
?
调用接口时能获得接口返回值的类型提示,增强类型安全
调用时直接写
const res = await api.get<YourType>(...)
step6:如何使用封装后的请求
// api/user.ts
import request from './request'export const getUserInfo = () => {return request.get<{ name: string; age: number }>('/user/info')
}
通过引入对象的形式直接调用里面的方法就可以了,然后声明泛型也就是我们如果需要返回值的话声明返回值的类型。然后后面是传过去的url访问的接口。实际上访问的地址是baseUrl+我们传过去的url。
总结