axios 是 Vue 官方推荐的一个 HTTP 库,用 axios 官方简介来介绍它,就是:vue
Axios 是一个基于 promise 的 HTTP 库,能够用在浏览器和 node.js 中。java
做为一个优秀的 HTTP 库,axios 战胜了曾经由 Vue 官方团队维护的 vue-resource,得到了 Vue 做者尤小右的大力推荐,成为了 Vue 项目中 HTTP 库的最佳选择。node
虽然,axios 是个优秀的 HTTP 库,可是,直接在项目中使用并非那么方便,因此,咱们须要对其进行必定程度上的配置封装,减小重复代码,方便调用。下面,咱们就来聊聊 Vue 中 axios 的封装。ios
其实,网上关于 axios 封装的代码很多,可是大部分都是在入口文件(main.js)中进行 axios 全局对象属性定义的形式进行配置,相似于以下代码:typescript
axios.defaults.timeout = 10000
复制代码
该方案有两个不足,首先,axios 封装代码耦合进入入口文件,不方便后期维护;其次,使用 axios 全局对象属性定义的方式进行配置,代码过于零散。json
针对问题一,我使用了 Vue 源码结构中的一大核心思想——将功能拆分为文件,方便后期的维护。单首创建一个 http.js
或者 http.ts
文件,在文件中引入 axios 并对其进行封装配置,最后将其导出并挂载到 Vue 的原型上便可。此时,每次修改 axios 配置,只须要修改对应的文件便可,不会影响到不相关的功能。axios
针对问题二,采用 axios 官方推荐的,经过配置项建立 axios 实例的方式进行配置封装。api
代码以下:跨域
// http.js
import axios from 'axios'
// 建立 axios 实例
const service = axios.create({
// 配置项
})
复制代码
baseURL 属性是请求地址前缀,将自动加在 url 前面,除非 url 是个绝对地址。正常状况下,在开发环境下和生产模式下有着不一样的 baseURL,因此,咱们须要根据不一样的环境切换不一样的 baseURL。promise
在开发模式下,因为有着 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)
})
复制代码
tips1:友情提示,上面那一坨 switch-case 代码,可使用策略模式进行优化~
tips2:若是有一些业务相关的需求,能够加在拦截器中,好比:loading、鉴权等~
因为前段时间,我在部门内推了 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
复制代码