使用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
对象。具体请自行查阅相关资料,不懂就按照这个来,官方也是这样的。