详细自定义封装Axios请求库,你还不会二次封装吗?
使用Vue
的时候,Axios
几乎已经是必用的请求库了,但是为了更方便搭配项目使用,很多开发者会选择二次封装,Vue3就很多人选择二次封装elementPlus
,那其实,Axios
我们也是经常会去封装的。
封装有什么好处呢?
首先,封装的目的主要是便于全局化
使用。
比如全局设置超时时间,固定接口的baseURL
,实现请求拦截
操作与响应拦截
操作。
那现在我就来展示一下我经常使用的封装套路。
封装功能
首先是功能上的封装,我们新建一个js
文件,我这里叫request.js
。
首先我们先导入axios和qs两个模块。
为什么要使用qs模块?
ajax请求的get请求是通过URL传参的(以?和&符连接),而post大多是通过json传参的。
qs是一个库。里面的stringify方法可以将一个json对象直接转为(以?和&符连接的形式)。
在开发中,发送请求的入参大多是一个对象。在发送时,如果该请求为get请求,就需要对参数进行转化。使用该库,就可以自动转化,而不需要手动去拼接
然后我这里还会用一个弹出层UI,我这里用elementUI
,你也可以选择其他UI,灵活变通
。但是最好不要全引入,单个引入弹出层组件就可以。
// 导入axios
import axios from 'axios'
//导入QS
import qs from 'qs'
// 使用element-ui Message用以消息提醒
import { Message} from 'element-ui';
导入之后,我们创建一个axios
的实例,可以理解为对象吧。
// 创建新的axios实例
const service = axios.create({
// 公共接口(暂未配置,预计写死)
baseURL: "http://localhost:8081/api",
// 超时时间 单位是ms
timeout: 20 * 1000,
})
Axios
的官方文档也说明了创建实例的方法。
然后里面有一些配置项,比如baseURL
,超时时间等,官网还要很多的配置,这里就不多说了。
此时这个实例service
就是我们要用的axios
了,你就当他是axios
的对象。
请求拦截器
文档也提供了拦截器设置方法,我们调用这个方法,自己封装一下请求与响应拦截。
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
官方的拦截器是这样的。
我这里喜欢用箭头函数,所以是这样的:
// 请求拦截器
service.interceptors.request.use(config => {
return config
}, error => {
Promise.reject(error)
})
这里携带的config
是一个数据配置项,每次发送请求后,整个axios的东西都会被我们获取到,然后我们这使用config
接收。
那既然这是一个axios的数据包,那我们就可以添加修改里面的数据。
我们看看它源码对应的代码段,是TS
写的,是一个泛型对象,对象中包含了一些设置参数。
图有些模糊,我贴个代码:
export interface AxiosRequestConfig<D = any> {
url?: string;
method?: Method;
baseURL?: string;
transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[];
transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[];
headers?: AxiosRequestHeaders;
params?: any;
paramsSerializer?: (params: any) => string;
data?: D;
timeout?: number;
timeoutErrorMessage?: string;
withCredentials?: boolean;
adapter?: AxiosAdapter;
auth?: AxiosBasicCredentials;
responseType?: ResponseType;
xsrfCookieName?: string;
xsrfHeaderName?: string;
onUploadProgress?: (progressEvent: any) => void;
onDownloadProgress?: (progressEvent: any) => void;
maxContentLength?: number;
validateStatus?: ((status: number) => boolean) | null;
maxBodyLength?: number;
maxRedirects?: number;
socketPath?: string | null;
httpAgent?: any;
httpsAgent?: any;
proxy?: AxiosProxyConfig | false;
cancelToken?: CancelToken;
decompress?: boolean;
transitional?: TransitionalOptions;
signal?: AbortSignal;
insecureHTTPParser?: boolean;
}
那我们就可以设置这些,至于这些配置项都是什么,我们可以前往官方文档查看。
在里面对基本上要操作的数据字段都写了注释。
请求拦截转换JSON数据:
config.data = qs.stringify(config.data);
用qs转化一下,原因前面已经说了。
设置固定请求头:
config.headers = {
//配置请求头
'Content-Type':'application/x-www-form-urlencoded'
}
携带参数/Token:
if (localStorage.getItem('token')) {
//携带token到axios参数
config.headers.Authorization = '固定携带的头部';
config.params = {
//固定携带参数
}
}
这里是从浏览器内存读取token
,你可以选择携带到头部。
当然,你也可以携带其他数据,也可以在config.params
中携带一些其他参数,每次请求都会默认携带到后端。
你也可以选择在cookie里面获取:
const token = getCookie('名称');//这里取token之前,需要先拿到token,存一下
if(token){
config.params = {'token':token} //如果要求携带在参数中
config.headers.token= token; //如果要求携带在请求头中
}
最后,不要忘记return config
,不然设置的字段不会生效。
然后我们Axios因为是基于Promise的,所以我们最后可以使用Promise.reject
捕捉他的错误信息。
Promise.reject
会在error
中返回一个Promise错误对象对象。
这里不懂请查阅Promise相关资料。
那为了方便查看,我就整个拦截器代码放出来了:
// 请求拦截器
service.interceptors.request.use(config => {
//发请求前做的一些处理,数据转化,配置请求头,设置token,设置loading等,根据需求添加
config.data = qs.stringify(config.data); //json数据转化
config.headers = {
'Content-Type':'application/x-www-form-urlencoded' //配置请求头
}
//注意使用token的时候需要引入cookie方法或者用本地localStorage等方法,推荐js-cookie
//判断localStorage是否存在token
if (localStorage.getItem('token')) {
//携带token到axios参数
config.headers.Authorization = '固定携带的头部';
config.params = {
//固定携带参数
}
}
// const token = getCookie('名称');//这里取token之前,需要先拿到token,存一下
// if(token){
// config.params = {'token':token} //如果要求携带在参数中
// config.headers.token= token; //如果要求携带在请求头中
// }
return config
}, error => {
Promise.reject(error)
})
这部分就是捕捉错误的代码。
响应拦截器
响应拦截器将会搭配elementUI的弹出层提示组件,当返回响应报错时,自动弹出提示,优化用户体验。
官方是这样写的:
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
那我们还是使用箭头函数来写,这里我先给出所以代码,在分段解析。
service.interceptors.response.use(response => {
console.log("进入响应拦截器");
//接收到响应数据并成功后的一些共有的处理,关闭loading等
return response
}, error => {
/***** 接收到异常响应的处理开始 *****/
if (error && error.response) {
// 根据响应码具体处理
switch (error.response.status) {
case 400:
error.message = '错误请求'
break;
case 401:
error.message = '未授权,请重新登录'
break;
case 403:
error.message = '拒绝访问'
break;
case 404:
error.message = '请求错误,未找到该资源'
window.location.href = "/NotFound"
break;
case 405:
error.message = '请求方法未允许'
break;
case 408:
error.message = '请求超时'
break;
case 500:
error.message = '服务器端出错'
break;
case 501:
error.message = '网络未实现'
break;
case 502:
error.message = '网络错误'
break;
case 503:
error.message = '服务不可用'
break;
case 504:
error.message = '网络超时'
break;
case 505:
error.message = 'http版本不支持该请求'
break;
default:
error.message = `连接错误${error.response.status}`
}
} else {
// 超时处理
if (JSON.stringify(error).includes('timeout')) {
Message.error('服务器响应超时,请刷新当前页')
}
error.message = '连接服务器失败'
}
Message.error(error.message)
/***** 处理结束 *****/
return Promise.resolve(error.response)
})
这里有一个返回的参数response
。
service.interceptors.response.use(response => {
console.log("进入响应拦截器");
//接收到响应数据并成功后的一些共有的处理,关闭loading等
return response
},
这个也是Promise
的,所以,我们在正常运行的时候,会正常进入方法,所以返回接收的数据。
如果出现错误,他是不会进入到上面的方法的,而是进入error
。
error => {
/***** 接收到异常响应的处理开始 *****/
if (error && error.response) {
// 根据响应码具体处理
switch (error.response.status) {
case 400:
error.message = '错误请求'
break;
case 401:
error.message = '未授权,请重新登录'
break;
case 403:
error.message = '拒绝访问'
break;
case 404:
error.message = '请求错误,未找到该资源'
window.location.href = "/NotFound"
break;
case 405:
error.message = '请求方法未允许'
break;
case 408:
error.message = '请求超时'
break;
case 500:
error.message = '服务器端出错'
break;
case 501:
error.message = '网络未实现'
break;
case 502:
error.message = '网络错误'
break;
case 503:
error.message = '服务不可用'
break;
case 504:
error.message = '网络超时'
break;
case 505:
error.message = 'http版本不支持该请求'
break;
default:
error.message = `连接错误${error.response.status}`
}else {
// 超时处理
if (JSON.stringify(error).includes('timeout')) {
Message.error('服务器响应超时,请刷新当前页')
}
error.message = '连接服务器失败'
}
Message.error(error.message)
/***** 处理结束 *****/
return Promise.resolve(error.response)
})
也就是进入以上代码。
那首先进入这个方法,我们先来一个判断。
if (error && error.response) {
//错误码判断
}else{
//超时处理
}
这个判断,我去除中间的部分,先看这个判断。
如果有error
对象,并且error
对象有response
参数时,我们此时就会确定这是请求状态错误。
为什么呢?因为error.response
中的status
会返回浏览器爆出的状态码。
那如果没有报状态码,那就说明非直接的错误,那就可能是超时了,我们在else
中进一步处理。
状态码处理
那我们还是先看直接错误处理:
我们获取到状态码,根据不同状态码弹出不同错误提示,这里我们将错误提示文字报错到这个error
中。
这里还只是保存错误信息,还没有调用elementUI弹出层哦!
是不是很方便呢?
进一步处理
else {
// 超时处理
if (JSON.stringify(error).includes('timeout')) {
Message.error('服务器响应超时,请刷新当前页')
}
error.message = '连接服务器失败'
}
那如果没有状态码,基本上就是超时,获取其他问题。
那我们if
判断一下看看是否超时,先使用JSON.stringify
将对象转化为字符串。
includes
方法是用于判断字符串中有没有对应字符串。
然后使用includes
判断有没有timeout
这个字符串,有就是超时了。
没有我们就默认给他抛出一个error.message = '连接服务器失败'
。
弹出提示:
不要忘了,我们还只是保存错误提示的字符串,没有调用elementUI的弹出层组件,我们最后调用一下。
Message.error(error.message)
调用后不要忘了返回参数,我们需要使用Promise.resolve
来返回一个error.response
。
Promise.resolve
作用是将参数转为Promise
对象。
具体请自行查阅相关资料,不懂就按照这个来,官方也是这样的。
暴露实例
最后不要忘记将整个封装后的实例暴露出去:
//暴露文件
export default service
全部代码
/**** 全局封装axios配置与消息 ****/
// 导入axios
import axios from 'axios'
//导入QS
import qs from 'qs'
// 使用element-ui Message用以消息提醒
import { Message} from 'element-ui';
// 创建新的axios实例
const service = axios.create({
// 公共接口(暂未配置,预计写死)
baseURL: "http://localhost:8081/api",
// 超时时间 单位是ms
timeout: 20 * 1000,
})
// 请求拦截器
service.interceptors.request.use(config => {
//发请求前做的一些处理,数据转化,配置请求头,设置token,设置loading等,根据需求添加
config.data = qs.stringify(config.data); //json数据转化
config.headers = {
'Content-Type':'application/x-www-form-urlencoded' //配置请求头
}
//注意使用token的时候需要引入cookie方法或者用本地localStorage等方法,推荐js-cookie
//判断localStorage是否存在token
if (localStorage.getItem('token')) {
//携带token到axios参数
config.headers.Authorization = '固定携带的头部';
config.params = {
//固定携带参数
}
}
// const token = getCookie('名称');//这里取token之前,需要先拿到token,存一下
// if(token){
// config.params = {'token':token} //如果要求携带在参数中
// config.headers.token= token; //如果要求携带在请求头中
// }
return config
}, error => {
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(response => {
console.log("进入响应拦截器");
//接收到响应数据并成功后的一些共有的处理,关闭loading等
return response
}, error => {
/***** 接收到异常响应的处理开始 *****/
if (error && error.response) {
// 根据响应码具体处理
switch (error.response.status) {
case 400:
error.message = '错误请求'
break;
case 401:
error.message = '未授权,请重新登录'
break;
case 403:
error.message = '拒绝访问'
break;
case 404:
error.message = '请求错误,未找到该资源'
window.location.href = "/NotFound"
break;
case 405:
error.message = '请求方法未允许'
break;
case 408:
error.message = '请求超时'
break;
case 500:
error.message = '服务器端出错'
break;
case 501:
error.message = '网络未实现'
break;
case 502:
error.message = '网络错误'
break;
case 503:
error.message = '服务不可用'
break;
case 504:
error.message = '网络超时'
break;
case 505:
error.message = 'http版本不支持该请求'
break;
default:
error.message = `连接错误${error.response.status}`
}
} else {
// 超时处理
if (JSON.stringify(error).includes('timeout')) {
Message.error('服务器响应超时,请刷新当前页')
}
error.message = '连接服务器失败'
}
Message.error(error.message)
/***** 处理结束 *****/
return Promise.resolve(error.response)
})
//暴露文件
export default service
封装请求信息
我们这只是封装了功能配置方面的代码,我们需要进一步封装需要携带的参数和baseURL
后面路径。
看看这个,注意,baseURL
与url
不是同一个东西。
baseURL是固定的请求地址,url是请求地址后的路径。
比如baseURL
是127.0.0.1/api/
,url
是/user
,那这样,请求地址就是,127.0.0.1/api/user
。
开始封装
创建一个js
文件,我这叫http.js
。
导入封装好功能的实例。
// 导入封装好的axios实例
import request from './request'
创建一个对象:
const http ={
//方法
}
里面可以写常用请求方法:
const http ={
/**
* methods: 请求
* @param url 请求地址
* @param params 请求参数
*/
get(url,params){
const config = {
method: 'get',
url:url
}
//如果非空,则添加参数,下文同理
if(params) config.params = params
//调用封装好的axios实例,下文同理
return request(config)
},
post(url,params){
const config = {
method: 'post',
url:url
}
if(params) {
config.data = params
console.log(config.data)
}
return request(config)
},
put(url,params){
const config = {
method: 'put',
url:url
}
if(params) config.params = params
return request(config)
},
delete(url,params){
const config = {
method: 'delete',
url:url
}
if(params) config.params = params
return request(config)
}
}
看看,这里面对不同请求都封装了方法,post
、get
、put
等等。
我们以post
方法为例:
post(url,params){
const config = {
method: 'post',
url:url
}
if(params) {
config.data = params
console.log(config.data)
}
return request(config)
},
携带了两个参数,url
和params
,params
是携带的参数。
创建一个配置对象config
,对象method
指定axios使用什么方法请求,url
就不必说了。
然后给出一个判断:
if(params) {
config.data = params
}
如果有参数传入,我们就给config
对象添加一个data
,将参数赋值给data
。
注意:
config
就当作axios
实例,所以字段是固定的,这里必须叫data
。
然后返回中调用request
,也就是axios
实例,将配置携带在里面,这样这个config
对象里面的配置就会与axios实例的字段信息相互补充,相当于为axios
实例增加了method
、url
以及数据(如果不为null
的话)。
这一层请求信息的封装也就好了,目的是补充配置。
封装请求方法
我们在封装一次调用方法,便于调用请求。
创建一个js
文件,我这是api.js
。
不罗嗦,贴上全部代码:
import http from '../utils/http'
/**
* @parms url 请求地址
* @param '/testIp'代表vue-cil中config,index.js中配置的代理
*/
// get请求
function getListAPI(url,params){
return http.get(`${url}`,params)
}
// post请求
function postFormAPI(url,params){
return http.post(`${url}`,params)
}
// put 请求
function putSomeAPI(url,params){
return http.put(`${url}`,params)
}
// delete 请求
function deleteListAPI(url,params){
return http.delete(`${url}`,params)
}
// 导出整个api模块
export default {
getListAPI,
postFormAPI,
putSomeAPI,
deleteListAPI
}
首先是导入上一层封装的请求信息。
import http from '../utils/http'
然后对应不同请求写不同方法。
以get为例:
// get请求
function getListAPI(url,params){
return http.get(`${url}`,params)
}
携带参数url
与params
,然后调用第二次封装的方法。
话说这儿我是借鉴了许多网上的封装形式总结的,但是这一次我感觉必要性不大,但是应该是有意义的,我也不明白,有大佬看到还麻烦点醒一番。
最后单个暴露每个请求模块就可以。
// 导出整个api模块
export default {
getListAPI,
postFormAPI,
putSomeAPI,
deleteListAPI
}
请求示范
这样调用起来也是挺方便的。
你只需要给出请求的后缀,比如你后端请求路径是/user
,那就直接:
api.postFormAPI("/user, {
//携带参数
topicUid: this.topic.topicUid,
}).then(
//.....
)