关于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 相关文章推荐
javascript里的条件判断
Feb 27 Javascript
LazyLoad 延迟加载(按需加载)
May 31 Javascript
jQuery ajax调用WCF服务实例
Jul 16 Javascript
2014 HTML5/CSS3热门动画特效TOP10
Dec 07 Javascript
js面向对象之静态方法和静态属性实例分析
Jan 10 Javascript
js如何实现淡入淡出效果
Nov 18 Javascript
JavaScript鼠标事件,点击鼠标右键,弹出div的简单实例
Aug 03 Javascript
JS实现可编辑的后台管理菜单功能【附demo源码下载】
Sep 13 Javascript
JavaScript获取URL中参数querystring的方法详解
Oct 11 Javascript
微信小程序开发之animation循环动画实现的让云朵飘效果
Jul 14 Javascript
React Native基础入门之调试React Native应用的一小步
Jul 02 Javascript
微信小程序工具函数封装
Oct 28 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
二十行语句实现从Excel到mysql的转化
2006/10/09 PHP
PHP 配置open_basedir 让各虚拟站点独立运行
2009/11/12 PHP
Google Voice 短信发送接口PHP开源版(2010.5更新)
2010/07/22 PHP
php数组函数序列之array_slice() - 在数组中根据条件取出一段值,并返回
2011/11/07 PHP
PHP统计nginx访问日志中的搜索引擎抓取404链接页面路径
2014/06/30 PHP
PHP使用curl模拟post上传及接收文件的方法
2016/03/04 PHP
PHP封装的非对称加密RSA算法示例
2018/05/28 PHP
JavaScript异步编程:异步数据收集的具体方法
2013/08/19 Javascript
浅析JS中document对象的一些重要属性
2014/03/06 Javascript
Javascript快速排序算法详解
2014/12/03 Javascript
node.js中的fs.realpath方法使用说明
2014/12/16 Javascript
jQuery中:checked选择器用法实例
2015/01/04 Javascript
JavaScript动态设置div的样式的方法
2015/12/26 Javascript
Vue.js组件使用开发实例教程
2016/11/01 Javascript
BootStrap+Mybatis框架下实现表单提交数据重复验证
2017/03/23 Javascript
JS实现列表页面隔行变色效果
2017/03/25 Javascript
Angular.js初始化之ng-app的自动绑定与手动绑定详解
2017/07/31 Javascript
jQuery Datatable 多个查询条件自定义提交事件(推荐)
2017/08/24 jQuery
jquery在vue脚手架中的使用方式示例
2017/08/29 jQuery
webpack 单独打包指定JS文件的方法
2018/02/22 Javascript
详解Vue中使用Axios拦截器
2019/04/22 Javascript
Nodejs技巧之Exceljs表格操作用法示例
2019/11/06 NodeJs
Vue快速实现通用表单验证的方法
2020/02/24 Javascript
Python+django实现文件下载
2016/01/17 Python
Python自动采集微信联系人的实现示例
2020/02/28 Python
django Layui界面点击弹出对话框并请求逻辑生成分页的动态表格实例
2020/05/12 Python
利用python对excel中一列的时间数据更改格式操作
2020/07/14 Python
出口公司经理求职简历中的自我评价
2013/10/13 职场文书
《再别康桥》教学反思
2014/02/12 职场文书
公证书样本
2014/04/10 职场文书
2014年监理工作总结范文
2014/11/17 职场文书
2014年乡镇安全生产工作总结
2014/12/02 职场文书
老公保证书怎么写
2015/02/26 职场文书
建党伟业的观后感
2015/06/01 职场文书
浅谈mysql执行过程以及顺序
2021/05/12 MySQL
Vue实现导入Excel功能步骤详解
2021/07/03 Vue.js