跟混亂的頁(yè)面彈窗說(shuō)再見(jiàn)
對(duì)于一些快速迭代的產(chǎn)品來(lái)說(shuō),特別是移動(dòng)端 C端產(chǎn)品,基于用戶運(yùn)營(yíng)的目的,在 app首頁(yè)給用戶展示各種各樣的彈窗是很常見(jiàn)的事情,在產(chǎn)品初期,由于迭代版本和運(yùn)營(yíng)策略變化地還不是太大,所以可能覺(jué)得沒(méi)什么,但當(dāng)產(chǎn)品運(yùn)營(yíng)到后期,各種八竿子打不著的運(yùn)營(yíng)策略輪番上陣,彈窗的樣式、邏輯等都變了不知道多少遍的時(shí)候,問(wèn)題就出來(lái)了

由于前期沒(méi)有做好規(guī)劃,首頁(yè)的彈窗組件可能放了十多個(gè)甚至更多,不僅是首頁(yè)有,首頁(yè)內(nèi)又引入了十多個(gè)個(gè)子組件,這些子組件內(nèi)也有,搞不好這些子組件內(nèi)還有子組件,子組件的子組件同樣還有彈窗,每個(gè)彈窗都有對(duì)應(yīng)的一組控制顯隱邏輯,分散在多個(gè)組件多個(gè)方法中,但是首頁(yè)只有一個(gè)頁(yè)面,你不可能讓所有符合顯示條件的彈窗,全都一下子彈出來(lái),反正我是沒(méi)見(jiàn)過(guò)這么做的 app,那么如何管理這些彈窗就成了頭等大事
而往往當(dāng)你意識(shí)到這一點(diǎn)的時(shí)候,很可能也正是局勢(shì)發(fā)展到無(wú)法控制的時(shí)候 治不了,等死吧
場(chǎng)景:A彈窗和 B彈窗位于主組件內(nèi),C彈窗位于主組件的子組件 C中,D彈窗位于主組件的子組件 B中,E彈窗位于主組件的子組件F的子組件G中

PM:我希望剛進(jìn)入這個(gè)頁(yè)面的時(shí)候,只有當(dāng) A彈窗 和 B彈窗以及 C彈窗,都不展示的時(shí)候,才展示 D彈窗,如果 D彈窗展示過(guò)了,除非 B彈窗之后又展示了一遍,否則無(wú)論什么情況下都不展示 E彈窗
FE:???
稍加思考一下,其實(shí)這件事情并不難辦,交給后端通過(guò)接口控制所有彈窗的顯隱就行了 主要是架構(gòu)的提前規(guī)劃,以及低耦合的代碼邏輯
彈窗的配置化
先確定一個(gè)大體思路,首先,必須要明確地知道當(dāng)前頁(yè)面共有哪些彈窗組件,包括頁(yè)面的子組件以及子組件的子組件內(nèi)的彈窗組件,這是必須的,否則你連有哪些組件都不知道怎么精確控制?
所以,還是上面那句話,提前規(guī)劃,防患于未然是很重要的,不然等頁(yè)面迭代了幾十版,當(dāng)初寫(xiě)代碼的人都不在了你才想到去統(tǒng)計(jì)一下頁(yè)面上到底有多少個(gè)彈窗,那真是夠你喝一壺的
那么就需要在一個(gè)地方統(tǒng)一把這些彈窗全記錄下來(lái),方便管理,于是可以得到下面這種數(shù)據(jù)結(jié)構(gòu):
// modalMap.js
export default {
// 記錄首頁(yè) index頁(yè)面內(nèi)的彈窗項(xiàng)
index: {
modalList: [{
name: 'modal_1',
level: 10,
show: true
}, {
name: 'modal_2',
level: 22,
show: true
}, {
name: 'modal_3',
level: 70,
show: true
}],
children: {
child1: {
modalList: [{
name: 'modal_1_1',
level: 8,
show: true
}, {
name: 'modal_1_2',
level: 62,
show: true
}],
children: {
child1_1: {
modalList: [{
name: 'modal_1_1_1',
level: 8,
show: true
}, {
name: 'modal_1_1_2',
level: 60,
show: true
}]
}
}
}
}
}
// ...還可以繼續(xù)記錄其他頁(yè)面的彈窗結(jié)構(gòu)
}
modalMap.js文件記錄每個(gè)頁(yè)面內(nèi)所有的彈窗項(xiàng),例如,首頁(yè) index內(nèi)的彈窗項(xiàng)都在屬性名index對(duì)于的值數(shù)據(jù)結(jié)構(gòu)中,index這個(gè)頁(yè)面主組件內(nèi)存在兩個(gè) modal,可以分別命名為 modal_1和 modal_2,如果 index這個(gè)頁(yè)面主組件的子組件內(nèi)也有 modal,則繼續(xù)嵌套,例如,index主組件的子組件 child1中也有 modal,那么就把 child1放到 index的 children中繼續(xù)記錄,以此類推
這種結(jié)構(gòu)看起來(lái)比較清晰,主組件及主組件內(nèi)的子組件內(nèi)的 modal都很清晰,一目了然,當(dāng)然,你可以不用這種結(jié)構(gòu),完全取決于你,這里就暫時(shí)這么定義
每個(gè) modal除了 name之外,還有 level 和 show屬性
level 用于標(biāo)識(shí)當(dāng)前 modal的層級(jí),每個(gè)頁(yè)面正常只能同時(shí)展示一個(gè) modal,但如果有多個(gè) modal都同一時(shí)間都滿足展示的條件,則對(duì)比它們的 level值,哪個(gè)大就優(yōu)先展示哪個(gè),其余的忽略掉,杜絕一個(gè)頁(yè)面可能提示展示多個(gè)彈窗的情況;
show屬性則是在 modal內(nèi)部來(lái)決定 modal最終是否展示,這樣一來(lái)就可以無(wú)視外界條件,很輕松地通過(guò)配置來(lái)禁止掉彈窗的顯示
通過(guò)發(fā)布/訂閱模式來(lái)管理彈窗
彈窗的配置結(jié)構(gòu)已經(jīng)確定了,下一步就是對(duì)這些配置的管理了
一般情況下,多個(gè)頁(yè)面同時(shí)滿足條件需要進(jìn)行展示的場(chǎng)景,大多數(shù)都是發(fā)生在剛進(jìn)入頁(yè)面,頁(yè)面發(fā)出多個(gè)請(qǐng)求,這些請(qǐng)求的返回結(jié)果分別控制對(duì)應(yīng)的一個(gè)彈窗的展示
因?yàn)榘l(fā)出去的這些請(qǐng)求很可能分屬于不同的業(yè)務(wù)線或部門(mén)管轄,相互獨(dú)立,所以說(shuō)如果把彈窗的控制權(quán)交給后端來(lái)做,其實(shí)是有點(diǎn)困難的,再加上請(qǐng)求是異步的,前端想要用意大利面條式代碼來(lái)保證彈窗之間的互斥性也不太容易,綜合起來(lái),也就導(dǎo)致了當(dāng)頁(yè)面上迭代出了數(shù)十個(gè)以上彈窗的時(shí)候,如果沒(méi)有提前規(guī)劃好,還是很容易出現(xiàn)彈窗同時(shí)展示的問(wèn)題的
這里暫時(shí)就以剛進(jìn)入頁(yè)面的情況為例,進(jìn)行邏輯梳理
首先,我需要知道頁(yè)面上有哪些彈窗可能會(huì)在剛進(jìn)入頁(yè)面的時(shí)候彈出來(lái)(即通過(guò)接口控制單個(gè)彈窗的展現(xiàn)與否),然后在所有彈窗的數(shù)據(jù)都拿到了的時(shí)候(即跟彈窗相關(guān)的接口都已經(jīng)返回?cái)?shù)據(jù)),才進(jìn)行彈窗的展示
這種情況比較適合使用發(fā)布/訂閱者模式,單個(gè)接口的數(shù)據(jù)返回就是一個(gè)訂閱,當(dāng)所有接口都訂閱之后,就進(jìn)行發(fā)布,也就是彈窗展示
// modalManage.js
class ModalManage {
constructor (modalList) {
this.modalFlatMap = {}
this.modalList = modalList
}
// ...
}
通過(guò) ModalManage類來(lái)管理彈窗,此類在初始化時(shí)接收一個(gè)參數(shù) modalList,這個(gè)參數(shù)其實(shí)就是剛進(jìn)入頁(yè)面時(shí),頁(yè)面上所有可能展示的彈窗(包括子組件的彈窗)的名稱集合,也就是必須要知道頁(yè)面上到底有多少個(gè)可能同時(shí)展示的彈窗,以上述示例代碼 modalMap.js為例, index頁(yè)面的 modalList值就是 ['modal_1', 'modal_2', 'modal_3', 'modal_1_1', 'modal_1_2', 'modal_1_1_1', 'modal_1_1_2']
這里其實(shí)直接傳彈窗數(shù)量就行了,index中有 7個(gè)彈窗可能同時(shí)展示,所以可以直接傳 7,我這里之所以要傳名稱進(jìn)去,實(shí)際上是為了方便調(diào)試,如果代碼出問(wèn)題了,比如頁(yè)面上實(shí)際有 5個(gè)接口可以控制 5個(gè)彈窗的展示,但你卻只訂閱了 4次,如果只傳數(shù)字,你就需要一個(gè)個(gè)找過(guò)去看是哪一個(gè)忘記訂閱了,但如果傳名稱,你一下子就能調(diào)試出來(lái),也就是代碼的可維護(hù)性會(huì)好一點(diǎn)
當(dāng)頁(yè)面上任意一個(gè)彈窗的狀態(tài)(即是否滿足展示的條件)確定下來(lái)后,就進(jìn)行訂閱操作:
// modalManage.js
add (name, dataInfo) {
// level, handler
if (this.modalList.indexOf(name) !== -1) {
if (!this.modalFlatMap[name]) {
this.modalFlatMap[name] = dataInfo
this.notify()
} else {
console.log('重復(fù)訂閱')
}
} else {
console.log('無(wú)效訂閱')
}
}
this.modalFlatMap是為了記錄訂閱列表,當(dāng)訂閱列表的長(zhǎng)度和 modalList相同時(shí),說(shuō)明所有的彈窗狀態(tài)都已經(jīng)準(zhǔn)備就緒,可以根據(jù)這些彈窗的優(yōu)先級(jí)進(jìn)行展示了,也就是 notify方法要做的事情
notify方法中,先排除掉屬性 show為 false的彈窗項(xiàng),再對(duì)比剩下的彈窗的 level,只展示 level最大的那個(gè)彈窗:
// modalManage.js
notify () {
if (Object.keys(this.modalFlatMap).length === this.modalList.length) {
const highLevelModal = Object.keys(this.modalFlatMap).filter(key => this.modalFlatMap[key].show).reduce((t, c) => {
return this.modalFlatMap[c].level > t.level ? this.modalFlatMap[c] : t
// 這個(gè) { level: -1 } 只是為了給 reduce函數(shù)一個(gè) initialValue,modal項(xiàng)的 level都應(yīng)該大于這個(gè) initialValue的 level值,即 -1
}, { level: -1 })
highLevelModal.handler()
}
}
使用單例模式管理嵌套組件以及多個(gè)頁(yè)面的彈窗
上述的 ModalManage類已經(jīng)足以管理彈窗了,但還有個(gè)問(wèn)題,如果一個(gè)頁(yè)面上的彈窗,分散位于頁(yè)面主組件及其子組件,甚至是子組件的子組件內(nèi),怎么辦?
這個(gè)時(shí)候就需要使用單例了
// 單例管理
const manageTypeMap = {}
// 獲取單例
function createModalManage (type) {
if (!manageTypeMap[type]) {
manageTypeMap[type] = new ModalManage(getAllModalList(modalMap[type]))
}
return manageTypeMap[type]
}
通過(guò) createModalManage這個(gè)方法來(lái)創(chuàng)建 ModalManage實(shí)例,根據(jù)傳入的 type來(lái)決定是否創(chuàng)建新的實(shí)例,如果單例管理對(duì)象 manageTypeMap中不存在 type對(duì)于的實(shí)例,則 new一個(gè) ModalManage實(shí)例,存入 manageTypeMap中,并返回這個(gè)新實(shí)例,否則就返回 manageTypeMap中已經(jīng)創(chuàng)建好了的實(shí)例
這樣一來(lái),無(wú)論彈窗分散在多少個(gè)組件內(nèi),無(wú)論這些組件嵌套得有多深,都能夠在保證代碼低耦合的前提下,順利地訂閱/發(fā)布事件
這里的 getAllModalList方法是個(gè)工具方法,用于從 modalMap中獲取頁(yè)面對(duì)應(yīng)的彈窗數(shù)據(jù)結(jié)構(gòu):
// util.js
const getAllModalList = modalInfo => {
let currentList = []
if (modalInfo.modalList) {
currentList = currentList.concat(
modalInfo.modalList.reduce((t, c) => t.concat(c.name), [])
)
}
if (modalInfo.children) {
currentList = currentList.concat(
Object.keys(modalInfo.children).reduce((t, c) => {
return t.concat(getAllModalList(modalInfo.children[c]))
}, [])
)
}
return currentList
}
至于 createModalManage的參數(shù)type,其值可以就是一個(gè)字符串,例如如果需要管理首頁(yè) index上可能同時(shí)展示的所有的彈窗,則可以將 type 的值指定為 index,在 index主組件以及其包含彈窗的子組件內(nèi),都通過(guò)這個(gè)字段來(lái)獲取 ModalManage單例對(duì)象:
const modalManage = createModalManage('index')
這樣做同時(shí)也解決了另外一個(gè)問(wèn)題,就是多個(gè)頁(yè)面的彈窗管理問(wèn)題,index頁(yè)面通過(guò) index創(chuàng)建 ModalManage單例,詳情頁(yè)就可以通過(guò) detail來(lái)創(chuàng)建 ModalManage單例,雙方互不干擾
總結(jié)
上述所有示例代碼已經(jīng)上傳到 github,有興趣地可以看下
本文只是對(duì)彈窗這么一種具體的案例進(jìn)行分析,實(shí)際上應(yīng)用于其他場(chǎng)景,例如頁(yè)面同一個(gè)位置的懸浮掛件管理等都是可行的
無(wú)論是彈窗的管理還是掛件的管理,放在 mvvm框架中,都是數(shù)據(jù)的管理,主流前端框架對(duì)于復(fù)雜的數(shù)據(jù)管理,都已經(jīng)有對(duì)應(yīng)的解決方案,例如 vuex 和 redux等,這些解決方案當(dāng)然也能夠解決上面的問(wèn)題
本文主要是對(duì)這種理念的探討,探討出一種通用的解決方案,無(wú)論你用的是 vue、react、angular還是jquery一把梭,亦或是微信小程序、支付寶小程序、快應(yīng)用等,都可以低成本地輕松套入使用
以上所述是小編給大家介紹的跟混亂的頁(yè)面彈窗說(shuō)再見(jiàn)詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Cookbook組件形式:優(yōu)化 Vue 組件的運(yùn)行時(shí)性能
本文仿照Vue Cookbook 組織形式,對(duì)優(yōu)化 Vue 組件的運(yùn)行時(shí)性能進(jìn)行闡述。通過(guò)基本的示例代碼給大家講解,需要的朋友參考下2018-11-11
vue中使用router全局守衛(wèi)實(shí)現(xiàn)頁(yè)面攔截的示例
這篇文章主要介紹了vue中使用router全局守衛(wèi)實(shí)現(xiàn)頁(yè)面攔截的示例,幫助大家維護(hù)自己的項(xiàng)目,感興趣的朋友可以了解下2020-10-10
詳解vue如何獲取當(dāng)前系統(tǒng)時(shí)間
這篇文章主要詳細(xì)介紹了vue如何獲取當(dāng)前系統(tǒng)時(shí)間,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01
Vue頁(yè)面跳轉(zhuǎn)動(dòng)畫(huà)效果的實(shí)現(xiàn)方法
百度了好久都沒(méi)辦法實(shí)現(xiàn)vue中一個(gè)頁(yè)面跳到另一個(gè)頁(yè)面,甚至到google上搜索也是沒(méi)辦法的,最終還是找了高人親自指導(dǎo),所以下面這篇文章主要給大家介紹了關(guān)于Vue頁(yè)面跳轉(zhuǎn)動(dòng)畫(huà)效果的實(shí)現(xiàn)方法,需要的朋友可以參考下2018-09-09
Vue中mintui的field實(shí)現(xiàn)blur和focus事件的方法
今天小編就為大家分享一篇Vue中mintui的field實(shí)現(xiàn)blur和focus事件的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08

