前言
弹窗,对于大家来说是司空见惯了。对于弹窗,我会分为两类:一种是模态框,另一种是业务定制弹窗。不管哪类,在开发中都应该有一套规范去管理这些弹窗,以便容易扩展和维护。
模态框一般可以通过引用组件库或者自己封装来实现重复调用(用到即调);而定制弹窗会根据产品的需求和节日不同定期进行修改更换,并且弹窗之前存在互斥关系,同时出现的话则根据优先级进行展示等。
背景
在业务大的场景下,可能会同时出现几个或者十几个互斥弹窗,即优先级最高的最先展示,关闭后自动出下一个优先级较高的弹窗,或者是弹窗到期之后自动出下一个弹窗等等这类场景。如果没有一个专门管理这种弹窗的方式,那么弹窗代码将会变得混乱并难以维护。
下面会用发布订阅思想来实现管理这类业务弹窗,当然也可以用promise队列的思想
来管理(大伙可以尝试实现),以及其他更好的方式。
发布订阅模式
说到发布订阅模式,那么大家可能会联想到另一个模式:观察者模式。实际上它们是有区别的,我区别它们的方式就是发布订阅模式中有个调度中心这个角色(如下图),其余的大伙自行百度下便可继续往下走。
(图片来源于网络,侵删)
实现思路
在撸代码之前我想有必要讲一下基本思路,讲一下我是如何将弹窗跟发布订阅联系起来的?
首先,如果同一时间需要根据优先级出10个弹窗,问题来了:
如何根据优先级进行确定先出哪个?
关闭第一个之后如何自动出现下一个优先级较高的?
如果优先级最高的弹窗过期了怎么处理?
这时候再想想发布订阅模式,我有一个调度中心,里面应该有个订阅队列管理10个弹窗;应该要有一个addDep的方法,去添加订阅者到订阅队列里面;那么对应的,有removeDep的方法,用来移除弹窗订阅者;哦对了,还应该有个notify方法,在添加订阅者和移除订阅者的时候去做一些事情,比如执行一些回调、重新计算优先级等等。
那么,上面的答案出来了:
在每个弹窗成功订阅的时候计算订阅队列谁的优先级最高,把它拎出来。
关闭一个弹窗之后,即移除一个订阅者之后重新计算优先级,把优先级最高的找出来,执行显示逻辑。
弹窗过期了,是不会进行addDep的,这个事情交给ajax去决定。
思路起源:这种思想主要是借鉴了Vue里面发布订阅的实现方式。
如果到这还是有点懵的话,直接上代码吧!
实现过程
初始弹窗列表
这列表用来配置当前业务有哪些弹窗。
NameSpace只是一个命名空间写法,不用太理会。
var NameSpace = Object.create(null);
//弹窗列表初始数据结构
NameSpace.modalList = [
{
id: 1,
//弹窗名字,唯一
name: "modal1",
//弹窗优先级
level: 10,
//是否显示弹窗,一开始默认不显示
isShow: false
},
{
id: 2,
name: "modal2",
level: 11,
isShow: false
},
{
id: 3,
name: "modal3",
level: 12,
isShow: false
}
]
发布订阅
发布订阅逻辑使用组合构造函数和原型模式实现。
传送门:谈谈组合构造函数和原型模式的今生前世
//发布订阅弹窗(调度中心)
function ModalManage(){
//总的弹窗名称队列
this.modalListName = NameSpace.modalList.reduce(function(arr, nextItem){
arr.push(nextItem.name);
return arr;
}, []);
//已经添加的队列
this.hasAddModalListName = [];
//重组弹窗的配置
this.modalOptions = {};
}
ModalManage.prototype.addDep = function(name, options){
if(!this.modalListName.includes(name)) return console.warn("无效订阅", name);
if(this.hasAddModalListName.includes(name)) return console.warn("重复订阅", name);
this.hasAddModalListName.push(name);
//当前订阅者的信息
var modalItem = NameSpace.modalList.filter(function(item){
return item.name == name;
})[0];
//重组配置
this.modalOptions[name] = {
id: modalItem.id,
level: modalItem.level,
isShow: options.isShow,
handler: options.handler
}
this.notify();
},
ModalManage.prototype.removeDep = function(name, handler){
if(this.modalOptions[name]){
//移除当前弹窗信息
delete this.modalOptions[name];
var delIndex = this.hasAddModalListName.indexOf(name);
this.hasAddModalListName.splice(delIndex, 1);
handler && handler();
this.notify();
}
}
ModalManage.prototype.notify = function(){
//收集相同优先级的弹窗
var sameLevelModalList = [];
//根据level和isShow拿到应该显示的最高级别的弹窗
var highLevelModal = Object.values(this.modalOptions).filter(function(item){
return item.isShow;
}).reduce(function(prev, next){
if(prev.level == next.level){
if(!sameLevelModalList.includes(prev)){
sameLevelModalList.push(prev);
}
sameLevelModalList.push(next);
}
return prev.level > next.level ? prev : next;
}, {level: -1});
//存在单个优先级最高的
if(!sameLevelModalList.length || highLevelModal.level > sameLevelModalList[0].level){
highLevelModal.handler && highLevelModal.handler();
}else{
//存在多个相同最高优先级的
sameLevelModalList.map(function(item){
item.handler && item.handler();
});
}
}
//发布订阅弹窗
NameSpace.modalDep = new ModalManage();
ModalManage构造函数作为发布订阅中的调度中心,里面维护一个总的弹窗队列和正在订阅中的弹窗队列。构造函数扩展三个方法,分别是上面提到的addDep 添加订阅者、removeDep 取消订阅和notify 通知方法。
这里着重提醒的是在addDep 和removeDep最后都会调用notify方法,作用是为了实现在关闭或者添加一个弹窗的时候重新去计算优先级,找到优先级最高的弹窗并自行弹窗回调handler方法。
而notify方法里面主要是根据isShow为true的前提下筛选出level最高的弹窗,所以你需要在一开始就应该知道哪些弹窗的优先级最高并赋予相应的值。同时支持显示多个相同优先级的弹窗,当然这个交互必须要符合你的场景,这个根据个人而定。
使用方式
function startDepDialog(){
//弹窗初始数据结构中多少个弹窗,就要对应addDep多少次,根据ajax返回结果来决定isShow是true还是false来显示弹窗,如果需要关闭弹窗,执行调度中心提供的removeDep方法来移除订阅者,此时调度中心会自动去寻找下一个层级高的弹窗来显示。
var modal1Flag = true;//这个结果一般由ajax返回结果来决定
NameSpace.modalDep.addDep("modal1", {
isShow: modal1Flag,
handler: function(){
//执行显示弹窗逻辑
console.log("订阅成功并显示modal1");
}
});
NameSpace.modalDep.addDep("modal2", {
isShow: true,
handler: function(){
console.log("订阅成功并显示modal2")
}
});
NameSpace.modalDep.addDep("modal3", {
isShow: true,
handler: function(){
console.log("订阅成功并显示modal3");
}
});
//移除弹窗3
NameSpace.modalDep.removeDep("modal3", function(){
console.log("成功移除弹窗3,此时总的订阅数为", NameSpace.modalDep.modalListName, "当前正在订阅的弹窗数为", NameSpace.modalDep.hasAddModalListName)
});
}
startDepDialog();
一般情况下,是否出弹窗一般由ajax返回结果决定,即addDep方法一般是写在ajax或者promise回调中进行添加弹窗订阅者。而removeDep可以通过事件的方式进行触发,例如弹窗关闭事件等。
执行上面代码结果为:
如上图,成功添加三个弹窗订阅者,此时移除优先级最高的弹窗3,再次找出下一个优先高的弹窗2,并进行显示。
以上,就是个人实现发布订阅弹窗的一种思路和过程,有需要可以直接应用到实际开发中。当然,如果有更好的方式进行管理这类弹窗,欢迎一起交流。
蟹蟹关注!