如何用發佈訂閱模式管理混亂彈窗

前言

彈窗,對於大家來說是司空見慣了。對於彈窗,我會分爲兩類:一種是模態框,另一種是業務定製彈窗。不管哪類,在開發中都應該有一套規範去管理這些彈窗,以便容易擴展和維護。

 

模態框一般可以通過引用組件庫或者自己封裝來實現重複調用(用到即調);而定製彈窗會根據產品的需求和節日不同定期進行修改更換,並且彈窗之前存在互斥關係,同時出現的話則根據優先級進行展示等。

 

背景

在業務大的場景下,可能會同時出現幾個或者十幾個互斥彈窗,即優先級最高的最先展示,關閉後自動出下一個優先級較高的彈窗,或者是彈窗到期之後自動出下一個彈窗等等這類場景。如果沒有一個專門管理這種彈窗的方式,那麼彈窗代碼將會變得混亂並難以維護。

 

下面會用發佈訂閱思想來實現管理這類業務彈窗,當然也可以用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,並進行顯示。

 

以上,就是個人實現發佈訂閱彈窗的一種思路和過程,有需要可以直接應用到實際開發中。當然,如果有更好的方式進行管理這類彈窗,歡迎一起交流。

 

蟹蟹關注!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章