关于Vue中axios的封装实例详解


Posted in Javascript onOctober 20, 2019

前言

axios 是 Vue 官方推荐的一个 HTTP 库,用 axios 官方简介来介绍它,就是:

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

作为一个优秀的 HTTP 库,axios 打败了曾经由 Vue 官方团队维护的 vue-resource,获得了 Vue 作者尤小右的大力推荐,成为了 Vue 项目中 HTTP 库的最佳选择。

虽然,axios 是个优秀的 HTTP 库,但是,直接在项目中使用并不是那么方便,所以,我们需要对其进行一定程度上的配置封装,减少重复代码,方便调用。下面,我们就来聊聊 Vue 中 axios 的封装。

开始

其实,网上关于 axios 封装的代码不少,但是大部分都是在入口文件(main.js)中进行 axios 全局对象属性定义的形式进行配置,类似于如下代码:

axios.defaults.timeout = 10000

该方案有两个不足,首先,axios 封装代码耦合进入入口文件,不方便后期维护;其次,使用 axios 全局对象属性定义的方式进行配置,代码过于零散。

针对问题一,我使用了 Vue 源码结构中的一大核心思想——将功能拆分为文件,方便后期的维护。单独创建一个 http.js 或者 http.ts 文件,在文件中引入 axios 并对其进行封装配置,最后将其导出并挂载到 Vue 的原型上即可。此时,每次修改 axios 配置,只需要修改对应的文件即可,不会影响到不相关的功能。

针对问题二,采用 axios 官方推荐的,通过配置项创建 axios 实例的方式进行配置封装。

代码如下:

// http.js
import axios from 'axios'
// 创建 axios 实例
const service = axios.create({
 // 配置项
})

根据环境设置 baseURL

baseURL 属性是请求地址前缀,将自动加在 url 前面,除非 url 是个绝对地址。正常情况下,在开发环境下和生产模式下有着不同的 baseURL,所以,我们需要根据不同的环境切换不同的 baseURL。

在开发模式下,由于有着 devServer 的存在,需要根据固定的 url 前缀进行请求地址重写,所以,在开发环境下,将 baseURL 设为某个固定的值,比如:/apis。

在生产模式下,根据 Java 模块的请求前缀的不同,可以设置不同的 baseURL。

具体代码如下:

// 根据 process.env.NODE_ENV 区分状态,切换不同的 baseURL
const service = axios.create({
 baseURL: process.env.NODE_ENV === 'production' ? `/java` : '/apis',
})

统一设置请求头

在这里和大家聊一个问题,什么是封装?在我看来,封装是通过更少的调用代码覆盖更多的调用场景。

由于,大部分情况下,请求头都是固定的,只有少部分情况下,会需要一些特殊的请求头,所以,在这里,我采用的方案是,将普适性的请求头作为基础配置。当需要特殊请求头时,将特殊请求头作为参数传入,覆盖基础配置。

代码如下:

const service = axios.create({
 ...
 headers: {
 get: {
  'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
  // 在开发中,一般还需要单点登录或者其他功能的通用请求头,可以一并配置进来
 },
 post: {
  'Content-Type': 'application/json;charset=utf-8'
  // 在开发中,一般还需要单点登录或者其他功能的通用请求头,可以一并配置进来
 }
 },
})

跨域、超时、响应码处理

axios 中,提供是否允许跨域的属性——withCredentials,以及配置超时时间的属性——timeout,通过这两个属性,可以轻松处理跨域和超时的问题。

下面,我们来说说响应码处理:

axios 提供了 validateStatus 属性,用于定义对于给定的HTTP 响应状态码是 resolve 或 reject promise。所以,正常设置的情况下,我们会将状态码为 2 系列或者 304 的请求设为 resolve 状态,其余为 reject 状态。结果就是,我们可以在业务代码里,使用 catch 统一捕获响应错误的请求,从而进行统一处理。

但是,由于我在代码里面使用了 async-await,而众所周知,async-await 捕获 catch 的方式极为麻烦,所以,在此处,我选择将所有响应都设为 resolve 状态,统一在 then 处理。

此部分代码如下:

const service = axios.create({
 // 跨域请求时是否需要使用凭证
 withCredentials: true,
 // 请求 30s 超时
 timeout: 30000,
 validateStatus: function () {
  // 使用async-await,处理reject情况较为繁琐,所以全部返回resolve,在业务代码中处理异常
  return true
 },
})

请求、响应处理

在不使用 axios 的情况下,每次请求或者接受响应,都需要将请求或者响应序列化。

而在 axios 中, transformRequest 允许在向服务器发送请求前,修改请求数据;transformResponse 在传递给 then/catch 前,允许修改响应数据。

通过这两个钩子,可以省去大量重复的序列化代码。

代码如下:

const service = axios.create({
 // 在向服务器发送请求前,序列化请求数据
 transformRequest: [function (data) {
  data = JSON.stringify(data)
  return data
 }],
 // 在传递给 then/catch 前,修改响应数据
 transformResponse: [function (data) {
  if (typeof data === 'string' && data.startsWith('{')) {
   data = JSON.parse(data)
  }
  return data
 }]
})

拦截器

拦截器,分为请求拦截器以及响应拦截器,分别在请求或响应被 then 或 catch 处理前拦截它们。

之前提到过,由于 async-await 中 catch 难以处理的问题,所以将出错的情况也作为 resolve 状态进行处理。但这带来了一个问题,请求或响应出错的情况下,结果没有数据协议中定义的 msg 字段(消息)。所以,我们需要在出错的时候,手动生成一个符合返回格式的返回数据。

由于,在业务中,没有需要在请求拦截器中做额外处理的需求,所以,请求拦截器的 resolve 状态,只需直接返回就可以了。

请求拦截器代码如下:

// 请求拦截器
service.interceptors.request.use((config) => {
 return config
}, (error) => {
 // 错误抛到业务代码
 error.data = {}
 error.data.msg = '服务器异常,请联系管理员!'
 return Promise.resolve(error)
})

再来聊聊响应拦截器,还是之前的那个问题,除了请求或响应错误,还有一种情况也会导致返回的消息体不符合协议规范,那就是状态码不为 2 系列或 304 时。此时,我们还是需要做一样的处理——手动生成一个符合返回格式的返回数据。但是,有一点不一样,我们还需要根据不同的状态码生成不同的提示信息,以方便处理上线后的问题。

响应拦截器代码如下:

// 根据不同的状态码,生成不同的提示信息
const showStatus = (status) => {
 let message = ''
 // 这一坨代码可以使用策略模式进行优化
 switch (status) {
  case 400:
   message = '请求错误(400)'
   break
  case 401:
   message = '未授权,请重新登录(401)'
   break
  case 403:
   message = '拒绝访问(403)'
   break
  case 404:
   message = '请求出错(404)'
   break
  case 408:
   message = '请求超时(408)'
   break
  case 500:
   message = '服务器错误(500)'
   break
  case 501:
   message = '服务未实现(501)'
   break
  case 502:
   message = '网络错误(502)'
   break
  case 503:
   message = '服务不可用(503)'
   break
  case 504:
   message = '网络超时(504)'
   break
  case 505:
   message = 'HTTP版本不受支持(505)'
   break
  default:
   message = `连接出错(${status})!`
 }
 return `${message},请检查网络或联系管理员!`
}

// 响应拦截器
service.interceptors.response.use((response) => {
 const status = response.status
 let msg = ''
 if (status < 200 || status >= 300) {
  // 处理http错误,抛到业务代码
  msg = showStatus(status)
  if (typeof response.data === 'string') {
   response.data = { msg }
  } else {
   response.data.msg = msg
  }
 }
 return response
}, (error) => {
 // 错误抛到业务代码
 error.data = {}
 error.data.msg = '请求超时或服务器异常,请检查网络或联系管理员!'
 return Promise.resolve(error)
})

tips:友情提示,上面那一坨 switch-case 代码,可以使用策略模式进行优化~

支持 TypeScript

由于前段时间,我在部门内推了 TypeScript,为了满足自己的强迫症,将所有 js 文件改写为了 ts 文件。由于 axios 本身有 TypeScript 相关的支持,所以只需要把对应的类型导入,然后赋值即可。

完整代码

// http.ts
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'

const showStatus = (status: number) => {
 let message = ''
 switch (status) {
 case 400:
  message = '请求错误(400)'
  break
 case 401:
  message = '未授权,请重新登录(401)'
  break
 case 403:
  message = '拒绝访问(403)'
  break
 case 404:
  message = '请求出错(404)'
  break
 case 408:
  message = '请求超时(408)'
  break
 case 500:
  message = '服务器错误(500)'
  break
 case 501:
  message = '服务未实现(501)'
  break
 case 502:
  message = '网络错误(502)'
  break
 case 503:
  message = '服务不可用(503)'
  break
 case 504:
  message = '网络超时(504)'
  break
 case 505:
  message = 'HTTP版本不受支持(505)'
  break
 default:
  message = `连接出错(${status})!`
 }
 return `${message},请检查网络或联系管理员!`
}

const service = axios.create({
 // 联调
 baseURL: process.env.NODE_ENV === 'production' ? `/` : '/apis',
 headers: {
 get: {
  'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
 },
 post: {
  'Content-Type': 'application/json;charset=utf-8'
 }
 },
 // 是否跨站点访问控制请求
 withCredentials: true,
 timeout: 30000,
 transformRequest: [(data) => {
 data = JSON.stringify(data)
 return data
 }],
 validateStatus () {
 // 使用async-await,处理reject情况较为繁琐,所以全部返回resolve,在业务代码中处理异常
 return true
 },
 transformResponse: [(data) => {
 if (typeof data === 'string' && data.startsWith('{')) {
  data = JSON.parse(data)
 }
 return data
 }]
})

// 请求拦截器
service.interceptors.request.use((config: AxiosRequestConfig) => {
 return config
}, (error) => {
 // 错误抛到业务代码
 error.data = {}
 error.data.msg = '服务器异常,请联系管理员!'
 return Promise.resolve(error)
})

// 响应拦截器
service.interceptors.response.use((response: AxiosResponse) => {
 const status = response.status
 let msg = ''
 if (status < 200 || status >= 300) {
  // 处理http错误,抛到业务代码
  msg = showStatus(status)
  if (typeof response.data === 'string') {
   response.data = {msg}
  } else {
   response.data.msg = msg
  }
 }
 return response
}, (error) => {
 // 错误抛到业务代码
 error.data = {}
 error.data.msg = '请求超时或服务器异常,请检查网络或联系管理员!'
 return Promise.resolve(error)
})

export default service

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
js传值 判断
Oct 26 Javascript
JS URL传中文参数引发的乱码问题
Sep 02 Javascript
学习并汇集javascript匿名函数
Nov 25 Javascript
Extjs4 GridPanel 的几种样式使用介绍
Apr 18 Javascript
深入分析JQuery和JavaScript的异同
Oct 23 Javascript
jQuery判断数组是否包含了指定的元素
Mar 10 Javascript
JS实现表单中checkbox对勾选中增加边框显示效果
Aug 21 Javascript
图文详解JavaScript的原型对象及原型链
Aug 02 Javascript
jQuery模拟实现的select点击选择效果【附demo源码下载】
Nov 09 Javascript
ES6正则的扩展实例详解
Apr 25 Javascript
JavaScript定时器setTimeout()和setInterval()详解
Aug 18 Javascript
在vue项目中使用codemirror插件实现代码编辑器功能
Aug 27 Javascript
JavaScript相等运算符的九条规则示例详解
Oct 20 #Javascript
Vue.js下拉菜单组件使用方法详解
Oct 19 #Javascript
vue实现二级导航栏效果
Oct 19 #Javascript
vue.js实现二级菜单效果
Oct 19 #Javascript
vue实现多级菜单效果
Oct 19 #Javascript
vue.js实现三级菜单效果
Oct 19 #Javascript
vue.js实现只能输入数字的输入框
Oct 19 #Javascript
You might like
php 传值赋值与引用赋值的区别
2010/12/29 PHP
使用php get_headers 判断URL是否有效的解决办法
2013/04/27 PHP
PHP笔记之:基于面向对象设计的详解
2013/05/14 PHP
彻底删除thinkphp3.1案例blog标签的方法
2014/12/05 PHP
PHP实现关键字搜索后描红功能示例
2019/07/03 PHP
js获取图片长和宽度的代码
2009/11/24 Javascript
jquery.boxy插件的iframe扩展代码
2010/07/02 Javascript
基于jquery的鼠标拖动效果代码
2012/05/30 Javascript
JavaScript对象创建及继承原理实例解剖
2013/02/28 Javascript
javascript实现文字图片上下滚动的具体实例
2013/06/28 Javascript
jquery 选取方法都有哪些
2014/05/18 Javascript
jquery获取radio值(单选组radio)
2014/10/16 Javascript
JS组件Bootstrap实现图片轮播效果
2016/05/16 Javascript
vue实现简单的MVVM框架
2018/08/05 Javascript
Vue源码解析之数组变异的实现
2018/12/04 Javascript
vue中根据时间戳判断对应的时间(今天 昨天 前天)
2019/12/20 Javascript
在Python中使用列表生成式的教程
2015/04/27 Python
Python实现登录人人网并抓取新鲜事的方法
2015/05/11 Python
python导入时小括号大作用
2017/01/10 Python
Python实现随机选择元素功能
2017/09/14 Python
Python 3.7新功能之dataclass装饰器详解
2018/04/21 Python
selenium+python自动化测试之页面元素定位
2019/01/23 Python
Python3几个常见问题的处理方法
2019/02/26 Python
Python 将 QQ 好友头像生成祝福语的实现代码
2020/05/03 Python
Python如何使用input函数获取输入
2020/08/06 Python
python实现计算器简易版
2020/12/17 Python
5分钟让你掌握css3阴影、倒影、渐变小技巧(小编推荐)
2016/08/15 HTML / CSS
深入解析HTML5 Canvas控制图形矩阵变换的方法
2016/03/24 HTML / CSS
澳大利亚最大的网上油画销售画廊:Direct Art Australia
2018/04/15 全球购物
中学生寄语大全
2014/04/03 职场文书
广播体操比赛口号
2014/06/10 职场文书
涨价通知怎么写
2015/04/23 职场文书
社区党员干部承诺书
2015/05/04 职场文书
银行中层干部培训心得体会
2016/01/11 职场文书
学校学习型党组织建设心得体会
2019/06/21 职场文书
mysql查看表结构的三种方法总结
2022/07/07 MySQL