使用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后面路径。

看看这个,注意,baseURLurl不是同一个东西。

baseURL是固定的请求地址,url是请求地址后的路径。

比如baseURL127.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)
    }
}

看看,这里面对不同请求都封装了方法,postgetput等等。

我们以post方法为例:

post(url,params){
        const config = {
            method: 'post',
            url:url
        }
        if(params) {
            config.data = params
            console.log(config.data) 
        }
        return request(config)
},

携带了两个参数,urlparamsparams是携带的参数。

创建一个配置对象config,对象method指定axios使用什么方法请求,url就不必说了。

然后给出一个判断:

if(params) {
    config.data = params
}

如果有参数传入,我们就给config对象添加一个data,将参数赋值给data

注意:config就当作axios实例,所以字段是固定的,这里必须叫data

然后返回中调用request,也就是axios实例,将配置携带在里面,这样这个config对象里面的配置就会与axios实例的字段信息相互补充,相当于为axios实例增加了methodurl以及数据(如果不为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)
}

携带参数urlparams,然后调用第二次封装的方法。

话说这儿我是借鉴了许多网上的封装形式总结的,但是这一次我感觉必要性不大,但是应该是有意义的,我也不明白,有大佬看到还麻烦点醒一番。

最后单个暴露每个请求模块就可以。

// 导出整个api模块
export default {
    getListAPI,
    postFormAPI,
    putSomeAPI,
    deleteListAPI
}

请求示范

这样调用起来也是挺方便的。

你只需要给出请求的后缀,比如你后端请求路径是/user,那就直接:

api.postFormAPI("/user, {
    //携带参数
   topicUid: this.topic.topicUid,
}).then(
    //.....
)
最后修改:2022 年 09 月 08 日
如果觉得我的文章对你有用,请随意赞赏