axios
在日常开发中,ajax请求是必不可少的。
本章目标
针对以上场景二次封装,减少代码量,并优化用户体验。 例:
// 新增或编辑数据时
// 验证表单
form.validate((valid: any) => {
if (valid) {
// 提交数据的ajax函数
submitFun(params).then((res: any) => {
// 业务成功后逻辑
})
}
});
// 登录时
// 验证表单
form.validate((valid: any) => {
if (valid) {
// 提交数据的ajax函数
submitFun(params).then((res: any) => {
// 业务成功后逻辑
// 登录成功后逻辑
}).catch(() => {
// 业务失败后逻辑
// 刷新验证码等逻辑
});
}
});
具体实现
加载遮罩
在实际交互中,请求数据时为了避免频繁请求和提升用户体验,我们会在请求时加一个遮罩。 在这里我们基于element-ui的Loading组件封装一个遮罩类
import { Loading } from 'element-ui'
let mask: any = null;
export default {
count: 0,
// 加载遮罩
loading(message: string) {
// 计数器加1
this.count++;
// console.log('loading,请求总数:', this.count);
mask = Loading.service({
lock: true,
text: message,
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
},
// 关闭遮罩
clear() {
const me = this as any;
if (me.count > 1) {
// 当计数器大于1时减1
me.count--;
// console.log('loadingend,请求总数减一:', me.count);
} else {
// console.log('loadingend,请求总数归0隐藏遮罩')
// 当计数器等于1时隐藏遮罩
// 计数器归0
me.count = 0;
mask.close();
}
}
}
基于axios和element-ui的Message组件,以及遮罩类封装request类
import axios from 'axios'
import { Message } from 'element-ui';
import mask from '@/utils/mask';
// 相关配置,方便移植到其他项目
const requestConfig = {
// 网络错误提示语
errorMessage: "您的网络不佳,请检查您的网络"
}
const service = axios.create({
withCredentials: true,
// api 的 base_url
// 在vue配置中设置,可以根据不同环境来配置不同的地址,避免出错
baseURL: process.env.VUE_APP_BASEURL
});
// 拦截请求
service.interceptors.request.use(
(config: any) => {
// 不管怎么样都要显示加载遮罩,否则遮罩类的计数逻辑会出问题
// toastMessage为自定义配置,在发起请求时传入
mask.loading(config.toastMessage);
return config;
},
error => {
return Promise.reject(error)
}
)
// 拦截请求结果
service.interceptors.response.use(
(res: any) => {
mask.clear();
return res
},
(error: any) => {
mask.clear();
// 网络错误错误是显示提示信息
if (error.message == "Network Error") {
Message({
message: requestConfig.errorMessage,
type: 'error',
customClass: "zZindex"
});
}
return Promise.reject(error)
}
)
export default {}
鉴权
前后端分离开发是现在主流开发模式,那么用户登录后在每次请求时都需要先后端提交token进行鉴权 在这里我们通过cookie保存token信息,这里先封装一个cookie类
import Cookies from 'js-cookie'
// 所有的cookie都加统一的前缀,前缀来至于vue项目配置,这样可以避免同域名下cookie污染
const appName = process.env.VUE_APP_NAME;
const cookies = {
/**
* @description 存储 cookie 值
* @param {String} name cookie name
* @param {String} value cookie value
* @param {Object} setting cookie setting
*/
set: function(name: string = 'default', value: string = '', cookieSetting: any = {}) {
const currentCookieSetting = {
expires: 1
}
Object.assign(currentCookieSetting, cookieSetting)
Cookies.set(`${appName}-${name}`, value, currentCookieSetting)
},
/**
* @description 拿到 cookie 值
* @param {String} name cookie name
*/
get: function(name: string = 'default') {
return Cookies.get(`${appName}-${name}`)
},
/**
* @description 拿到 cookie 全部的值
*/
getAll: function() {
return Cookies.get()
},
/**
* @description 删除 cookie
* @param {String} name cookie name
*/
remove: function(name: string = 'default') {
return Cookies.remove(`${appName}-${name}`)
}
}
export default cookies
在这里我们可以再次封装request类
import axios from 'axios'
import { Message, MessageBox } from 'element-ui';
import mask from '@/utils/mask';
import cookies from '@/utils/store/cookies'
import { get } from 'lodash';
// 相关配置,方便移植到其他项目
const requestConfig = {
// 判断请求是否成功的节点名称
successProperty: "code",
// 请求成功返回值,预留配置,后面用到
successValue: '200',
// 登录过期返回值
expiredValue: 401,
// token名称,在这里存取都是同一个名称
tokenName: 'Authorization',
// 像后端发起请求时token数据前缀
tokenPrefixes: 'Bearer ',
// 网络错误提示语
errorMessage: "您的网络不佳,请检查您的网络"
}
const service = axios.create({
withCredentials: true,
// api 的 base_url
// 在vue配置中设置,可以根据不同环境来配置不同的地址,避免出错
baseURL: process.env.VUE_APP_BASEURL
});
// 拦截请求
service.interceptors.request.use(
(config: any) => {
// 获取存储在cookies中的token
const token = cookies.get(requestConfig.tokenName);
if (token && token != 'undefined') {
// 请求头自动添加token
config.headers[requestConfig.tokenName] = `${requestConfig.tokenPrefixes} ${token}`;
}
// 不管怎么样都要显示加载遮罩,否则遮罩类的计数逻辑会出问题
// toastMessage为自定义配置,在发起请求时传入
mask.loading(config.toastMessage);
return config;
},
error => {
return Promise.reject(error);
}
)
// 拦截请求结果
service.interceptors.response.use(
(res: any) => {
mask.clear();
// 校验登录是否过期
const data = res.data;
if (data && get(data, requestConfig.successProperty) == requestConfig.expiredValue) {
// 退出登录逻辑
}
return res;
},
(error: any) => {
mask.clear();
// 网络错误错误是显示提示信息
if (error.message == "Network Error") {
Message({
message: requestConfig.errorMessage,
type: 'error',
customClass: "zZindex"
});
}
return Promise.reject(error);
}
)
export default {}
常用请求封装
模拟请求
在实际开发过程中,可能会存在开发时后端并未介入的情况,我们可以在request类新增一个模拟异步方法。这样只需要和后端预定好数据格式和字段名称即可开发,减少不必要的等待时间。
/**
* 获取模拟异步方法,你传入什么数据就返回什么数据
*
* @export
* @param {*} data
* @return {*} 模拟数据
*/
export function getJson(data: any):any {
return new Promise((resolve) => {
resolve({
// 模拟返回成功状态
code: requestConfig.successValue,
// 传入的数据
data: data,
// 分页用
total: 40
});
});
}
如果以业务逻辑来区分ajax请求,一般分为一下几种
发起请求后,业务成功才执行后续动作,可能会有提示信息。业务失败一般都会有提示信息。多用于数据的增删改查.
发起请求后,业务失败和成功分别有不同的后续业务动作,一般都会有提示信息。多用于业务流处理,登录等场景。
针对以上场景,我们可以先封装一个基类方法,这个方法用来处理后端返回的数据,业务失败和成功分别走不同的逻辑,在使用时我们只需要通过then()与catch()来区分即可
/** * 基础提交数据函数 * 成功失败都有回调 * * @export * @param {*} data axios配置 * @param {*} options 额外配置 [{ * successCode = requestConfig.successValue, 提交成功状态码 * isSuccessToast = false,提交成功是否显示提示 * isNoToast = false 是否隐藏提示,为ture时不会有任何提示 * toastMessage = "Loading..." 遮罩提示文字 * }={}] * @return {*} */ export function axiosRequest(data: any, { successCode = requestConfig.successValue, isSuccessToast = false, isNoToast = false, toastMessage = "Loading..." }: any = {}): Promise<any> { return new Promise((resolve, reject) => { // 设置遮罩提示文字 data.toastMessage = toastMessage; service.request(data).then((res: any) => { const data = res.data, // 判断提交数据结果 success = get(data, requestConfig.successProperty) == successCode, // 确定消息提示类型 type = success ? 'success' : 'error'; // 只要isNoToast不为true,请求失败必然提升 // isSuccessToast为ture时,请求成功也会提示 if (isSuccessToast || !success) { const mes = data.msg; // 登录过期错误不做提示 if (!isNoToast && data.code != requestConfig.expiredValue) { Message({ message: mes, type: type, customClass: "zZindex" }); } } // 标识请求结果状态 data.success = success; if (success) { resolve(res); } else { reject(res); } }).catch(() => { // 请求失败 reject({ success: false, data: null }); }); }); }
在这个基础方法中我们对后端数据进行了处理,并针对常用场景进行了配置化处理,但是这个方法并不推荐直接使用,我们需要针对这个方法再次扩展一个针对场景2的方法
/** * 提交数据函数 * 成功失败都有回调 * @export * @param {*} data axios配置 * @param {*} options 额外配置 [{ * successCode = requestConfig.successValue, 提交成功状态码 * isSuccessToast = false,提交成功是否显示提示 * isNoToast = false 提交失败是否隐藏提示 * }={}] * @returns */ export function ajaxBack(data: any, options?: any) { return new Promise((resolve, reject) => { axiosRequest(data, options).then((res: any) => { // 一般来说前端只需要res.data中的数据 resolve(res.data); }).catch(res => { reject(res); }); }); }
对于场景1,我们可以在场景2的基础上二次封装,一般来说可以分为get、post两个方法
/**
* get 方式提交数据
* 无错误回调
* @export
* @param {string} url 地址
* @param {*} [params] 参数
* @param {*} options 额外配置 [{
* successCode = requestConfig.successValue, 提交成功状态码
* isSuccessToast = false,提交成功是否显示提示
* isNoToast = false 提交失败是否隐藏提示
* }={}]
* @returns
*/
export function ajax(url: string, params?: any, options?: any) {
return new Promise((resolve) => {
ajaxBack({
url: url,
method: "get",
params: params
},
options
).then(res => {
resolve(res);
}).catch(() => { });
});
}
/**
* 通过data提交数据,可设置数据提交方式
* 无错误回调
* @export
* @param {string} method 数据提交方式
* @param {string} url 地址
* @param {*} [params] 参数
* @param {*} options 额外配置 [{
* successCode = requestConfig.successValue, 提交成功状态码
* isSuccessToast = false,提交成功是否显示提示
* isNoToast = false 提交失败是否隐藏提示
* }={}]
* @return {*}
*/
export function ajaxDataMethod(method: string, url: string, params?: any, options?: any): Promise<any> {
return new Promise((resolve) => {
ajaxBack({
url: url,
method: method,
data: params
},
options
).then(res => {
resolve(res);
}).catch(() => { });
});
}
/**
* POST 方式提交数据
* 无错误回调
* @export
* @param {string} url 地址
* @param {*} [params] 参数
* @param {*} options 额外配置 [{
* successCode = requestConfig.successValue, 提交成功状态码
* isSuccessToast = false,提交成功是否显示提示
* isNoToast = false 提交失败是否隐藏提示
* }={}]
* @returns
*/
export function ajaxP(url: string, params?: any, options?: any) {
return ajaxDataMethod("POST", url, params, options)
}
另外你也可以根据业务需求再次封装一些常用场景,例:
/**
* PATCH 方式提交数据
* 无错误回调
* @export
* @param {string} url 地址
* @param {*} [params] 参数
* @param {*} options 额外配置 [{
* successCode = requestConfig.successValue, 提交成功状态码
* isSuccessToast = false,提交成功是否显示提示
* isNoToast = false 提交失败是否隐藏提示
* }={}]
* @returns
*/
export function ajaxH(url: string, params?: any, options?: any) {
return ajaxDataMethod("PATCH", url, params, options)
}
/**
* put 方式提交数据
* 无错误回调
* @export
* @param {string} url 地址
* @param {*} [params] 参数
* @param {*} options 额外配置 [{
* successCode = requestConfig.successValue, 提交成功状态码
* isSuccessToast = false,提交成功是否显示提示
* isNoToast = false 提交失败是否隐藏提示
* }={}]
* @returns
*/
export function ajaxPut(url: string, params?: any, options?: any) {
return ajaxDataMethod("PUT", url, params, options)
}
/**
* delete 方式提交数据
* 无错误回调
* @export
* @param {string} url 地址
* @param {*} [params] 参数
* @param {*} options 额外配置 [{
* successCode = requestConfig.successValue, 提交成功状态码
* isSuccessToast = false,提交成功是否显示提示
* isNoToast = false 提交失败是否隐藏提示
* }={}]
* @returns
*/
export function ajaxDelete(url: string, params?: any, options?: any) {
return ajaxDataMethod("DELETE", url, params, options)
}
使用
假如我们有一个系统模块,针对不同接口我们可以这样定义,例:
import {
ajax, ajaxP, ajaxPut, ajaxDelete, ajaxBack
} from '@/utils/data/request';
// 系统列表
export function list(params: any) {
return ajaxBack({
url: `system/sys/list`,
method: "get",
params: params
})
}
// 删除系统
export function del(id: any) {
return ajaxDelete(`system/sys/${id}`, null, {
isSuccessToast: true
})
}
// 新建系统
export function add(params: any) {
return ajaxP('system/sys', params, {
isSuccessToast: true
})
}
// 修改系统
export function edit(params: any) {
return ajaxPut('system/sys', params, {
isSuccessToast: true
})
}
// 系统详情
export function info(params: any) {
return ajax(`system/sys/${params.id}`)
}
// 系统改变状态
export function status(params: any) {
return ajaxPut(`system/sys/changeStatus/${params.id}/${params.status}`, null, {
isSuccessToast: true
})
}
// 导出
export function exportExcel(params: any) {
return ajax('system/sys/export', params)
}
通过封装request类,我们可以做到用最少的代码来应对最多变的后端🐶
结语
本文主要针对pc端场景进行了封装,那么移动端应该如何封装呢?
本文对每个请求都做了自动遮罩处理,那么是否存在不需要自动遮罩的场景,这种需求如何处理?
上一章我们讲了数据代理如何实现,那么在实际场景中,本章内容如何与数据代理结合使用?
假如后端返回的数据格式五花八门,格式不统一,如何处理?
最后更新于
这有帮助吗?