前言
最近寫的
移動端業務
常常跟彈框
打交道,偶爾處理對於多個彈框的顯示
問題也是捉襟見肘
,特別是產品常常改需求
,那麼有沒有一種優雅的解決方案去處理上面這種問題
,或者說,淘寶
、拼多多
等是怎麼處理這種問題的前端
因爲項目一開始沒有作好規劃
或者說一開始就不是你維護的
,致使首頁的彈窗組件可能放了十多個甚至更多
,不只是首頁有,首頁內又引入了十多個個子組件
,這些子組件內也有彈框
,另外子組件的子組件也可能存在彈框
,每一個彈窗都有對應的一組控制顯隱邏輯
,可是你不可能讓全部符合顯示條件的彈窗都全都一會兒在首頁彈出來,如何有順序的管理這些彈框是重中之重
的事情git
一個小場景
上面這麼分析可能有同窗仍是不瞭解這個
業務痛點
,咱們舉個例子,假設首頁頁面有個A組件,A組件有一個彈框A_Modal
須要在打開首頁顯示出來,enen...很簡單,咱們按照平時的邏輯請求後端接口拿到數據去控制彈框顯示
就行,咱們繼續接着迭代
,此時遇到了一個B組件,一樣也是要顯示在首頁
,由於是新活動,因此優先級比較大
,須要顯示B_Modal彈框
,這時候你可能要去找找控制A組件的接口
,找到後端說這個組件不顯示了或者說本身手動重置爲false
,一個組件能夠這樣搞,可是幾十個呢?,不太現實
github
以下圖:後端
這些彈框是都要在首頁上顯示的彈框設計模式
小誤區
❗️注意如下這種交互彈框不在咱們討論範圍以內,好比經過按鈕彈出彈框這種,像這類彈框經過
交互事件
咱們控制就行,咱們要處理的彈框場景是經過後端接口來顯示彈框
,因此後面咱們所說的彈框都是這種狀況
,注意便可api
帶着這個業務痛點
,我去踩坑了幾種方案,下面來分享下如下這種配置化彈框
方案(借鑑了動態表單的思路來實現
)數組
配置化彈框
以前寫
管理後臺系統
的時候有了解過動態表單
,實際就是經過一串JSON數據渲染出表單
,那麼咱們是否是能夠基於這種思路,經過可配置化的數據
來控制彈框的顯示
,顯然是能夠的緩存
// modalConfig.js export default { // 首頁 index: { // 彈框列表 modalList: [{ id: 1, // 彈框的id name: 'modalA', level: 100, // 彈框的優先級 // 由前端控制彈框是否顯示 // 當咱們一個活動過去了廢棄一個彈框時候,能夠不須要經過後端去更改 frontShow: true }, { id: 2, name: 'modalB', level: 122, frontShow: true }, { id: 3, name: 'modalC', level: 70, frontShow: true }] } }
這樣作的好處就是利於管理彈框
,而且最重要的一點,我能夠知道個人頁面有多少彈框
,一目瞭然的去配置
,這裏咱們先講解下每一個彈框modal的屬性
函數
id
:彈框id-彈框的惟一idname
: 彈框名稱-能夠根據名稱很快找到該頁面上的彈框level
: 彈框優先級-杜絕一個頁面可能提示展現多個彈窗的狀況frontShow
: 前端控制彈框顯示的字段-默認爲true
backShow
: 後端控制彈框顯示的字段-經過接口請求獲取
發佈訂閱模式來管理彈框
配置完彈框數據,咱們還缺乏一個
調度系統去統一管理這些彈框
,這時候天然而然就能夠想到發佈訂閱這種設計模式學習
// modalControl.js class ModalControl { constructor () { // ... } // 訂閱 add () { // ... this.nodify() } // 發佈 notify () { // ... } }
正常狀況下,後端單個接口
會返回給咱們字段來控制彈框的顯示
,固然也可能存在多個接口去控制
彈框的顯示,對於這些狀況,咱們前端本身去作一層合併
,只要保證最後得出一個控制彈框是否展現的字段就行
,此時咱們就能夠在相應的位置取註冊咱們的彈框類便可
那何時發佈呢
?
注意這裏的發佈跟咱們平時的發佈
判斷狀況可能不同,之前咱們可能經過在一個生命週期鉤子或者按鈕觸發等事件去發佈
,可是咱們仔細想一想,進入首頁由接口控制顯示
,這樣動做的發生須要2個條件
- 每次發生一次訂閱操做都伴隨着一次執行一次
預檢測
操做,檢測全部的彈框是否都訂閱完
- 真正觸發的時機是當前頁面的彈框都訂閱完了,由於只有這樣才能拿到全部彈框的優先級,才能判斷顯示哪一個彈框
初版實現
根據上面的分析
單個接口返回的就是一個訂閱
,而發佈是等到全部的彈框都訂閱完才執行
,因而咱們能夠快速寫出如下代碼結構
class ModalControl { constructor () { // ... } // 訂閱 add () { // ... this.preCheck() } // 預檢測 preCheck(){ if(this.modalList.length === n){ // ... this.notify() } } // 發佈 notify () { // ... } }
實現這個彈框類
,咱們來拆分實現這四個方法
就好了
constructor構造函數
根據以上思路,
ModalControl類
的 constructor方法中須要設置的初始值差很少也就知道了
// 上述彈框配置 import modalMap from './modalMap' constructor (type) { this.type = type // 頁面類型 this.modalFlatMap = {} // 用於緩存全部已經訂閱的彈窗的信息 this.modalList = getAllModalList(modalMap[this.type]) // 該頁面下全部須要訂閱的彈框列表,數組長度就是n值 } // 彈框信息 modalInfo = { name: modalItem.name, level: modalItem.level, frontShow: modalItem.frontShow, backShow: infoObj.backShow, handler: infoObj.handler // 表示選擇出了須要展現的彈窗時,該執行的函數 }
constructor
構造函數接收一個全部彈框的配置項
,裏面聲明兩個屬性
,modalFlatMap
用於緩存全部已經訂閱的彈窗的信息
,modalList
表示該頁面下全部須要訂閱的彈框列表
,數組長度就是n值
add訂閱
咱們以彈框的
id
的做爲惟一key
值,當請求後端數據接口成功後
,在該請求方法相應的回調裏進行訂閱操做
,而且每次訂閱都會去檢測下調用preCheck
方法來判斷當前頁面的全部彈框是否已經訂閱完
,若是是
,則觸發notify
add (modalItem, infoObj) { this.modalFlatMap[modalItem.name] = { id: modalItem.id, level: modalItem.level, frontShow: modalItem.frontShow, backShow: infoObj.backShow, handler: infoObj.handler } this.preCheck() }
preCheck檢測
preCheck
這個方法很簡單,單純的用來判斷當前頁面的彈框
是否都訂閱完成
if (this.modalList.length === Object.values(this.modalFlatMap).length) { this.notify() }
notify發佈
當咱們
頁面上的彈框所有都訂閱完後
就會觸發notify
發佈,這個notify
主要作了這麼一件事情:過濾不須要顯示的彈框,篩選出當前頁面須要顯示而且優先級最高的彈框,而後觸發其handler方法
notify () { const highLevelModal = Object.values(this.modalFlatMap).filter(item => item.backShow && item.frontShow).reduce((t, c) => { return c.level > t.level ? c : t }, { level: -1 }) highLevelModal.handler && highLevelModal.handler() }
單例模式完善ModalControl
到上面的步驟,其實咱們的
彈框管理類
已經差很少完成了,可是考慮到彈框可能分佈在子組件或者孫組件等等
,這時候若是都在每一個組件實例化彈框類
,那麼他們實際是沒有關聯的
,此時單例模式就派上用場了
const controlTypeMap = {} // 獲取單例 function createModalControl (type) { if (!controlTypeMap[type]) { controlTypeMap[type] = new ModalControl(type) } console.log('controlTypeMap[type]', controlTypeMap[type]) return controlTypeMap[type] } export default createModalControl
初版代碼
初版的代碼就這樣完成了,是否是很簡單,搭配
modalConfig
和發佈訂閱模式
,咱們能夠處理大部分問題了,爲本身打個call
😊
class ModalControl { constructor (type) { this.type = type this.modalFlatMap = {} this.modalList = getAllModalList(modalMap[this.type]) } add (modalItem, infoObj) { this.modalFlatMap[modalItem.name] = { id: modalItem.id, level: modalItem.level, frontShow: modalItem.frontShow, backShow: infoObj.backShow, handler: infoObj.handler } this.preCheck() } preCheck () { if (this.modalList.length === Object.values(this.modalFlatMap).length) { this.notify() } } notify () { const highLevelModal = Object.values(this.modalFlatMap).filter(item => item.backShow && item.frontShow).reduce((t, c) => { return c.level > t.level ? c : t }, { level: -1 }) highLevelModal.handler && highLevelModal.handler() } } const controlTypeMap = {} // 獲取單例 function createModalControl (type) { if (!controlTypeMap[type]) { controlTypeMap[type] = new ModalControl(type) } console.log('controlTypeMap[type]', controlTypeMap[type]) return controlTypeMap[type] } export default createModalControl
demo驗證一下
初版的代碼例子🌰在該倉庫下demo,執行如下操做就可
git clone git@github.com:vnues/modal-control.git git checkout feature/first yarn yarn serve
第二版
初版的
ModalControl
能夠解決咱們開發中遇到的場景
,可是咱們還要考慮一下複雜場景
接下來,咱們來完善咱們的彈框類ModalControl
,咱們先來分析下須要注意哪些問題吧
- 可能存在多個接口控制彈框顯示(好比A接口也能夠調取這個彈框,後面持續迭代,B接口也可能調取這個彈框),因此再也不是那種
一對一的關係
,而是多對一的關係,多個接口均可以控制這個彈框的顯示
,這裏經過apiFlag
來標識彈框,再也不使用name
得益於咱們的modalConfig配置
,咱們只須要補充一個apiFlag
字段,即可以解決上述問題,是否是很方便,其實後續的複雜場景,也在這裏補充字段完善就行
modalConfig
增長apiFlag字段,
由name字段對應彈框
變爲apiFlag
對應彈框,實現多對一的關係
export default { // 首頁 index: { // 彈框列表 modalList: [{ id: 1, // 彈框的id name: 'modalA', level: 100, frontShow: true, apiFlag: ['mockA_1', 'mockA_2'] }, { id: 2, name: 'modalB', level: 122, frontShow: true, apiFlag: ['mockB_1', 'mockB_2'] }, { id: 3, name: 'modalC', level: 70, frontShow: true, apiFlag: ['mockC_1'] }] } }
第二版代碼
/* eslint-disable no-console */ /* eslint-disable no-unused-vars */ import modalMap from './modalConfig' const getAllModalList = mapObj => { let currentList = [] if (mapObj.modalList) { currentList = currentList.concat( mapObj.modalList.reduce((t, c) => t.concat(c.id), []) ) } if (mapObj.children) { currentList = currentList.concat( Object.values(mapObj.children).reduce((t, c) => { return t.concat(getAllModalList(c)) }, []) ) } return currentList } const getModalItemByApiFlag = (apiFlag, mapObj) => { let mapItem = null // 首先查找 modalList const isExist = (mapObj.modalList || []).some(item => { if (item.apiFlag === apiFlag || (Array.isArray(item.apiFlag) && item.apiFlag.includes(apiFlag))) { mapItem = item } return mapItem }) // modalList沒找到,繼續找 children if (!isExist) { Object.values(mapObj.children || []).some(mo => { mapItem = getModalItemByApiFlag(apiFlag, mo) return mapItem }) } return mapItem } class ModalControl { constructor (type) { this.type = type this.modalFlatMap = {} // 用於緩存全部已經訂閱的彈窗的信息 this.modalList = getAllModalList(modalMap[this.type]) // 該頁面下全部須要訂閱的彈框列表,數組長度就是n值 } add (apiFlag, infoObj) { const modalItem = getModalItemByApiFlag(apiFlag, modalMap[this.type]) console.log('modalItem', modalItem) this.modalFlatMap[apiFlag] = { level: modalItem.level, name: modalItem.name, frontShow: modalItem.frontShow, backShow: infoObj.backShow, handler: infoObj.handler } this.preCheck() } preCheck () { if (this.modalList.length === Object.values(this.modalFlatMap).length) { this.notify() } } notify () { const highLevelModal = Object.values(this.modalFlatMap).filter(item => item.backShow && item.frontShow).reduce((t, c) => { return c.level > t.level ? c : t }, { level: -1 }) highLevelModal.handler && highLevelModal.handler() } } const controlTypeMap = {} // 獲取單例 function createModalControl (type) { if (!controlTypeMap[type]) { controlTypeMap[type] = new ModalControl(type) } console.log('controlTypeMap[type]', controlTypeMap[type]) return controlTypeMap[type] } export default createModalControl
demo驗證一下
初版的代碼例子🌰在該倉庫下demo,執行如下操做就可
git clone git@github.com:vnues/modal-control.git git checkout feature/second yarn yarn serve
待解決問題
細心的童鞋可能會發現,居然初版和第二版分別實現了
一對一
和多對一
的關係,那麼一對多的關係
如何實現呢?也便是多個接口一塊兒決定彈框是否展現
這裏我給出兩種思路
:
多個接口一塊兒決定彈框是否展現
,咱們徹底能夠在接口層作合併,最終實現出來的效果就是一對一
- 訂閱方法作去重,利用高階函數再次封裝對應的
handler
實現多個接口一塊兒決定彈框是否展現
,我的仍是推薦第一種解決方案
個人前端學習筆記📒
最近花了點時間把筆記整理到語雀上
了,方便童鞋們閱讀
總結
- 文中若有錯誤,歡迎在評論區指正,若是這篇文章幫到了你,歡迎點贊和關注😊