场景
在前端中,尤其是移动端,常要求接口调用时唤起loading组件,然后在接口返回数据后隐藏。
通常为了繁琐的显示调用loading的show方法,我们会在全局的接口配置中设置。
同时,主流的组件库会通过设置单例模式来避免一个页面出现多个loading组件的情况。
但单例模式并不能解决这个场景:一个页面有多个需要loading组件接口请求。
如果仅仅只是设置了单例模式,那么loading组件就会在第一个接口返回数据后被隐藏。
这当然是可以利用Promise.all来解决,但又失去全局配置的效果,每次多接口调用都需要组织一个Promise.all,然后显示的调用和隐藏loading。
然而这个问题是可以通过扩展传统单例模式来解决的,
解决方案
即除了为loading配置单例模式外,增加一个计数器,当配置了需要loading的接口调用时,使计数器加一。
每次接口数据的返回,先使计数器减一,再判断计数器是否小于等于零,如果是,则清除loading
程序流程图
发送接口请求时
接口返回数据时
源码实现
每此接口调用都要求一个LoadConfig类的实例,如果没有手动传入,则初始化一个默认的LoadConfig类的实例。
LoadingConfig类
import { Toast } from 'vant';
export class LoadConfig {
static loading = null;
clearLoading() {
LoadConfig.loading = null;
}
constructor({
needLoading = true,
needError = true,
needSuccess = false,
loadingMsg = '加载中...',
errorMsg = '',
successMsg = '',
} = {}) {
this.needLoading = needLoading; // 是否需要loading
this.needError = needError; // 是否显示错误提示
this.needSuccess = needSuccess; // 是否显示成功提示
this.loadingMsg = loadingMsg; // loading的文案
this.errorMsg = errorMsg; // 错误提示文案
this.successMsg = successMsg; // 成功提示文案
this.$loading = null; // loading实例
}
// 初始化loading并做不需要loading情况的拦截
loading() {
if (!this.needLoading) {
return;
}
// 实例不存在则初始化实例
if (!LoadConfig.loading) {
this.$loading = LoadConfig.loading = Toast.loading({
duration: 0,
message: this.loadingMsg,
});
const clear = LoadConfig.loading.clear;
// 扩展clear方法
this.$loading.clear = LoadConfig.loading.clear = (...args) => {
LoadConfig.clearLoading();
return clear.apply(LoadConfig.loading, args);
};
}
// loading是单例,所以要记录调用了几次 等所以接口都返回数据后再清除loading
LoadConfig.loading._count ? (LoadConfig.loading._count += 1) : (LoadConfig.loading._count = 1);
}
// 失败时调用的钩子
fail(msg) {
this._beforeResult();
if (!this.needError) {
return;
}
// 如果需要显示错误信息,则显示错误信息并强制隐藏loading
Toast.fail({
duration: 1000,
message: this.errorMsg || msg,
});
}
success(msg) {
this._beforeResult();
if (!this.needSuccess) {
return;
}
// 如果需要显示成功信息,则显示成功信息并强制隐藏loading
Toast.success({
duration: 1000,
message: this.successMsg || msg,
});
}
// 当计数小于对于零时,清除loading
clearLoading() {
if (!LoadConfig.loading) {
return;
}
LoadConfig.loading._count--;
if (LoadConfig.loading && LoadConfig.loading._count <= 0) {
LoadConfig.loading.clear();
}
}
// 每次接口返回数据(无论成功失败)都会调用的钩子
_beforeResult() {
this.needLoading && this.clearLoading();
}
}
接口配置
import axios from 'axios'
const Api = {}
Api.get = (url, config = new LoadConfig()) => {
config.loading();
return new Promise((resolve, reject) => {
axios
.get(url)
.then(response => {
config.success();
resolve(response);
})
.catch(error => {
config.fail(error);
reject(error);
});
});
};
Api.post = (url, data, config = new LoadConfig()) => {
config.loading();
return new Promise((resolve, reject) => {
axios
.post(url, data)
.then(response => {
config.success();
resolve(response);
})
.catch(error => {
config.fail(error);
reject(error);
});
});
};
export default Api