经验|axios封装与api管理:简洁实用
一、先看封装前后的效果
1.封装前后效果对比
封装后的axios结合es6使用,如下图,看起来简洁干净,用起来方便实用
封装前可能是这样的,如下图
二、选择这种封装方式的原因
封装前后两种方式,在具体接口调用上都是差不多的,主要是在声明和组织接口上有“肉眼可见”的差距,封装后的axios再结合es6的箭头函数,简洁干净实用了,要是花里胡哨,中看不中用又麻烦,那不值得记录。多年经验,见过许多种axios封装和接口组织方式,这种最受我欢迎的,原因如下:
1.简洁干净
2.实用不麻烦
3.好管理便于维护
三、封装过程与结果
1.封装过程
1.安装axios插件,创建 api 文件夹,并在文件夹下床架 axios.js 文件,并在文件中,创建引入 axios,然后创建axios实例
// npm 安装 axios 命令:npm install axios -s
// yarn 安装 axios 命令:yarn add axios -s
import axios from 'axios'
// 创建axios实例
const http = axios.create({
// 请求头配置 token
headers: {
'token': localStorage.getItem('token') || ''
},
// 基础路径
baseURL: basePath,
// 请求连接超时设置
timeout: 2 * 60 * 1000,
// 表示跨域请求时是否需要使用凭证,开启后,后端服务器要设置允许开启
withCredentials: true,
})
2.完善 axios 请求与响应拦截器
// request 请求拦截器
http.interceptors.request.use(
config => {
// 发起请求时,重新获取最新的token,这一步有时很重要,因为创建 axios 实例的时候,
// 获取到的 token 未必是有效的,或说未必能获取到
const token = localStorage.getItem('token')
// 特殊配置:设置下载获取 excel 接口的返回值为 blob。这在异步下载文件时有时会显得很有用。
if (config.url === '/api/excel/download') {
config.responseType = 'blob'
}
// 特殊配置:登录接口,将 请求的头的 token 设置为空字符串
if (config.url === '/author/login') {
config.headers['token'] = ''
} else {
config.headers['token'] = token || ''
}
return config
},
error => {
console.warn(error)
return Promise.reject(error)
}
)
// response 响应拦截器
http.interceptors.response.use(
response => {
const res = response.data
// 特殊配置:code为 -10086,表示资源不存在,跳转到提示页(404)
if (res.code === -10086) {
router.push({
name: 'ErrorPage',
query: { type: '404' }
}).then()
return res
}
// 特殊配置:-12306,无权限,跳转到提示页
if (res.code === -12306) {
router.push({
name: 'ErrorPage',
query: { type: 'authority' }
}).then()
return res
}
// code === -12345,token失效,跳转到登录页重新登陆
if (res.code === -12345) {
/* // 一个特殊功能:访问一个地址没权限,记录下该地址,登录获取后可以重定向到该地址
let currentUrl = window.location.href
const index = currentUrl.indexOf('#')
const str = currentUrl.substr(index + 1, 6)
// 如当前页不是登录页则跳转到登录页并缓存当前页地址
if (str !== '/Login') {
localStorage.setItem('currentUrl', window.location.href)
router.push({ name: 'Login' }).then()
} */
router.push({ name: 'Login' }).then()
return res
}
// 特殊配置:异步下载文件中有用,一般用不上,导出 word、excel 文件接口
// 注意此时返回的是完整的 response,开放更多信息便于处理下载过程
const url = response.config.url
if (url === '/api/word/download' || url === '/api/excel/download') {
response.headers.responseType = 'blob'
return response
}
// 前后端约定 code 为100表示接口正常响应,取相反值则表示此时接口非正常响应,
// 此时,统一开启接口返回的业务类型的错误提示,这样就不用在每个接口都处理,
// 例如账号密错误时,自动捕获接口返回的业务类型的“错误”提示。注意这是业务类型的“错误”,不是bug。
if (res.code !== 100) {
Message({
message: res.message || '服务器开小差啦,请稍后重试',
type: 'error',
duration: 3 * 1000
})
// 直接reject有时非常有用,统一拦截,这时,调用接口的回调.then()不会在执行,
// 而是改为执行.catch(),这样做的好处是我们无需考虑在具体的每一个接口中
// 的.then()是否是正常响应,只接受正常响应的 code 为 100 的。非100不进入.then()
// 当然这对后端按约定编写接口有较高的要求,也有弊端,就是非100无法在.then()中接收到。
// return Promise.reject(res.message)
return res
} else {
return res
}
},
error => {
/* // 特殊处理:拦截更改提示信息
if (error.message.indexOf('timeout') > -1) {
error.message = '请求超时'
}
if (error.message.indexOf('Network') > -1) {
error.message = '网络错误'
} */
/* // 特殊处理:禁止短时间内多次弹出提示,遗憾的是效果不理想,后续测验优化
// 1000毫秒内最多只展示一次报错信息
clearTimeout(timeout)
timeout = setTimeout(() => {
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
}, 1000) */
Message({
message: error.message,
type: 'error',
duration: 3 * 1000
})
return Promise.reject(error)
}
)
3.axios结合Promise封装出5常规请求,即是get,post,put,delete,以及用于上传文件的postMultipart
4.根据业务类型分模块创建 api 文件,组织管理接口,调用封装的5种请求方式
// api文件夹中的 user.js
// 用户模块相关api
import {
http,
basePath,
post,
get,
put,
deleteJson,
postMultipart,
} from './axios'
let userApi = {
login: (p) => post('/auth/login', p),
}
export default userApi
5.汇总挂载到 vue 实例上,减少引入,方便调用
在 api 文件夹下,创建 index.js 文件,用于汇总 各模块接口,挂载到 vue 实例上,方便调用
// index.js 文件
import userApi from "./user";
import fileApi from "./file";
import orderApi from "./order";
const api = {
...userApi, // 用户相关接口
...fileApi, // 文件相关接口
...orderApi, // 订单相关接口
}
export default api
在入口文件 main.js 中引入 api 文件夹下的 index.js 并挂载到 vue 实例上。
// main.js 文件
import api from './api/index'
Vue.prototype.$api = api
6.具体页面功能中调用接口
// login.vue 调用
login () {
const params = {
account: this.account,
// 密码加密传输,加密技术自选,这里以 jsencrypt 为例
password: encrypt(this.password),
}
// 开启登录按钮的 loading
this.loading = true
this.$api.login(params).then(res => {
// 处理相应的业务
console.log(res)
}).catch(error => {
// catch 这一步已经在拦截器中统一处理了,所以实际中不用写catch,特殊情况例外。
console.log(error)
}).finally(() => {
// 关闭loading
this.loading = false
})
},
2.封装结果
1.完整的 axios.js 文件
2.完整的 user.js 文件
3.完整的 login.vue 文件