从零到一封装Axios请求
请求的封装主要是为了满足项目中各种业务场景下与后端交互,简化请求方法,做到快速处理应对
请求的类型
- GET
- POST
- DELETE
- PUT
GET上送参数类型
?name=123&age=21
POST请求头类型
- Content-Type: application/json : 请求体中的数据会以json字符串的形式发送到后端
- Content-Type: application/x-www-form-urlencoded:请求体中的数据会以普通表单形式(键值对)发送到后端
- Content-Type: multipart/form-data: 它会将请求体的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。
Axios封装
了解了以上知识,下面我们可以一步一步去封装,去完善
Axios的请求适用场景 下面示例就是最简单的封装,不添加任何场景适配的封装,后面我们会针对当前最简版做渐进式增强
import axios from 'axios'
const service = axios.create({
baseURL: process.env.VUE_APP_instanceURL,
timeout: 10000
})
service.interceptors.request.use(config => {
return config
}, error => {
return Promise.reject(error)
})
service.interceptors.response.use(response => {
return response
}, error => {
return Promise.reject(error)
})
一、增加全局loading控制
在页面发起请求的时候有一些在请求过程中禁止用户任何操作,这个时候就需要loading,但是并非所有接口都需要,所以需要区分处理,这里我们用
elementUI的组件库示例,其他组件库也都抛出了js中使用loading的方法,用法基本相同,贴上elementUI中 Loading的使用链接,点击了解
import axios from 'axios'
import { Loading } from 'element-ui'
let loading // 在这里声明是为了在下边全局使用
const loadingWhiteList = [ // 白名单,这些请求不需要loading
'/api/web/login',
'/api/web/user'
]
const service = axios.create({
baseURL: process.env.VUE_APP_instanceURL,
timeout: 10000
})
service.interceptors.request.use(config => {
// 白名单内不加入loading
!loadingWhiteList.includes(config.url) && (loading = Loading.service({
text: '加载中...', // 设置加载中提示文案
spinner: 'el-icon-loading', // 设置加载中图标样式
background: 'rgba(0,0,0,0.2)', // 设置遮罩
fullscreen: true, // 设置是否全屏,全屏时禁止一切操作
customClass: 'globalLoadingWrapper' // 设置全局Loading的类名去做样式定制化
}))
return config
}, error => {
loading && loading.close()
return Promise.reject(error)
})
service.interceptors.response.use(response => {
loading && loading.close()
return response
}, error => {
loading && loading.close()
return Promise.reject(error)
})
二、封装方法,不同的上送参数类型支持设置不同的请求头
在上述,我们已经了解请求头的类型,比如说
formdata时,请求头要设置成Content-Type: multipart/form-data,为了便于适配,我们让使用者传入相应的请求头,当用户没有传入,那就使用默认的这里主要涉及的是请求头的合并,把传入的与默认的合并,并以传入的为主
// @/utils/fetch.js
const service = axios.create({
baseURL: process.env.VUE_APP_instanceURL,
timeout: 10000
})
// 设置默认的post请求头
service.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
...
const fetch = (method, url, data, config) => {
let configSeting = Object.assign({}, {
paramsSerializer: function (params) {
return qs.stringify(params, { arrayFormat: 'brackets' })
},
...config,
params: data
})
switch (method) {
case 'get':
return service.get(url, configSeting)
default:
return service[method](url, data, config)
}
}
export default fetch
// 使用
import fetch from '@/utils/fetch.js'
// 获取用户信息
export function getUserinfo (parmas) {
return fetch('get', '/api/web/userinfo', parmas,{timeout:30000})
}
// 上送用户信息
export function updateUserinfo (data) {
return fetch('post', '/api/web/updateUserinfo', data,{
timeout:30000,
headers:{
'Content-Type':'multipart/form-data'
}
})
}
三、返回数据处理,状态拦截
返回数据首先要处理的是错误码,这个错误码是与后端协商定的,比如6001是已退出登录或者token失效,根据返回的错误码做出对应的处理,登录失效就返回登录页等等
service.interceptors.response.use(response => {
loading && loading.close()
// 状态拦截
response.data && response.data.code && errorCode(code)
return response
}, error => {
loading && loading.close()
const { message } = error
// 错误处理
errorMessage(message)
return Promise.reject(error)
})
// 全局状态码处理
const errorCode = (code) => {
// 如果多的话可以用switch
let timer
if (code === 6001) {
Message.error('登录失效')
timer = setTimeout(() => {
clearTimeout(timer)
router.replace('/login')
}, 200)
}
}
// 接口错误提示
const errorMessage = (message) => {
if (message.includes('Network Error')) {
message = '网络环境异常'
} else if (message.includes('timeout')) {
message = '后端接口请求超时'
} else if (message.includes('Request failed with status code')) {
const code = message.substr(message.length - 3)
message = '后端接口' + code + '异常'
}
Message.error(message)
}
四、切换页面终止白名单以外的请求
上述已经基本封装好了axios,那么在路由发生变化时,之前页面的请求可能就没有用了,这个时候为了优化性能,去终止之前的请求
当然,这里有一些接口可能不能一概而论,所以还是要有白名单不可终止
以下使用的是接口名称去做的key,不建议使用
// 终止请求的载体 Map不可重复
window.globalCancelRequest = new Map()
// 终止请求的白名单载体
window.globalRequestWhiteList = [
'/api/web/login'
]
const addPending = (config) => {
config.cancelToken = new axios.CancelToken((cancel) => {
window.globalCancelRequest.set(config.url, cancel)
})
}
const removePending = (config) => {
if (window.globalCancelRequest.get(config.url)) {
window.globalCancelRequest.get(config.url)()
window.globalCancelRequest.delete(config.url)
}
}
// 取消白名单外所有请求 在路由跳转前调用
export const cancelAllPrevRequest = () => {
Array.from(window.globalCancelRequest).forEach(([key, cancel]) => {
if (!window.globalRequestWhiteList.includes(key)) {
cancel()
}
})
}
service.interceptors.request.use(config => {
// 白名单内不加入loading
!loadingWhiteList.includes(config.url) && (loading = Loading.service({
text: '加载中...', // 设置加载中提示文案
spinner: 'el-icon-loading', // 设置加载中图标样式
background: 'rgba(0,0,0,0.2)', // 设置遮罩
fullscreen: true, // 设置是否全屏,全屏时禁止一切操作
customClass: 'globalLoadingWrapper' // 设置全局Loading的类名去做样式定制化
}))
// 存放请求 优先调用避免同一个请求重复调用
removePending(config)
addPending(config)
return config
}, error => {
loading && loading.close()
return Promise.reject(error)
})
service.interceptors.response.use(response => {
loading && loading.close()
// 从pending请求列表移除
removePending(response.config)
// 状态拦截
response.data && response.data.code && errorCode(response.data.code)
return response
}, error => {
loading && loading.close()
// 从pending请求列表移除
error.config && removePending(error.config)
// 错误处理
const { message } = error
errorMessage(message)
return Promise.reject(error)
})
Congratulations 恭喜,到此,你已经可以尝试自己封装了
贴上全部代码,仅供参考
import axios from 'axios'
import qs from 'qs'
import { Loading, Message } from 'element-ui'
import router from '@/router'
let loading // 在这里声明是为了在下边全局使用
const loadingWhiteList = [ // 白名单,这些请求不需要loading
'/api/web/login',
'/api/web/user'
]
// 终止请求的载体 Map不可重复
window.globalCancelRequest = new Map()
// 终止请求的白名单载体
window.globalRequestWhiteList = [
'/api/web/login'
]
const addPending = (config) => {
config.cancelToken = new axios.CancelToken((cancel) => {
window.globalCancelRequest.set(config.url, cancel)
})
}
const removePending = (config) => {
if (window.globalCancelRequest.get(config.url)) {
window.globalCancelRequest.get(config.url)()
window.globalCancelRequest.delete(config.url)
}
}
// 取消白名单外所有请求 在路由跳转前调用
export const cancelAllPrevRequest = () => {
Array.from(window.globalCancelRequest).forEach(([key, cancel]) => {
if (!window.globalRequestWhiteList.includes(key)) {
cancel()
}
})
}
const service = axios.create({
baseURL: process.env.VUE_APP_instanceURL,
timeout: 10000
})
// 设置默认的post请求头
service.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
service.interceptors.request.use(config => {
// 白名单内不加入loading
!loadingWhiteList.includes(config.url) && (loading = Loading.service({
text: '加载中...', // 设置加载中提示文案
spinner: 'el-icon-loading', // 设置加载中图标样式
background: 'rgba(0,0,0,0.2)', // 设置遮罩
fullscreen: true, // 设置是否全屏,全屏时禁止一切操作
customClass: 'globalLoadingWrapper' // 设置全局Loading的类名去做样式定制化
}))
// 存放请求 优先调用避免同一个请求重复调用
removePending(config)
addPending(config)
return config
}, error => {
loading && loading.close()
return Promise.reject(error)
})
service.interceptors.response.use(response => {
loading && loading.close()
// 从pending请求列表移除
removePending(response.config)
// 状态拦截
response.data && response.data.code && errorCode(response.data.code)
return response
}, error => {
loading && loading.close()
// 从pending请求列表移除
error.config && removePending(error.config)
// 错误处理
const { message } = error
errorMessage(message)
return Promise.reject(error)
})
// 全局状态码处理
const errorCode = (code) => {
// 如果多的话可以用switch
if (code === 6001) {
Message.error('登录失效')
let timer = setTimeout(() => {
clearTimeout(timer)
router.replace('/login')
}, 500)
}
}
// 接口错误提示
const errorMessage = (message) => {
if (message.includes('Network Error')) {
message = '网络环境异常'
} else if (message.includes('timeout')) {
message = '后端接口请求超时'
} else if (message.includes('Request failed with status code')) {
const code = message.substr(message.length - 3)
message = '后端接口' + code + '异常'
}
Message.error(message)
}
const fetch = (method, url, data, config) => {
let configSeting = Object.assign({}, {
paramsSerializer: function (params) {
return qs.stringify(params, { arrayFormat: 'brackets' })
},
...config,
params: data
})
switch (method) {
case 'get':
return service.get(url, configSeting)
default:
return service[method](url, data, config)
}
}
export default fetch