首先,我需要知道页面上有哪些弹窗可能会在刚进入页面的时候弹出来(即通过接口控制单个弹窗的展现与否),然后在所有弹窗的数据都拿到了的时候(即跟弹窗相关的接口都已经返回数据),才进行弹窗的展示
这种情况比较适合使用发布/订阅者模式,单个接口的数据返回就是一个订阅,当所有接口都订阅之后,就进行发布,也就是弹窗展示
// modalManage.js class ModalManage { constructor (modalList) { this.modalFlatMap = {} this.modalList = modalList } // ... }
通过 ModalManage类来管理弹窗,此类在初始化时接收一个参数 modalList,这个参数其实就是刚进入页面时,页面上所有可能展示的弹窗(包括子组件的弹窗)的名称集合,也就是必须要知道页面上到底有多少个可能同时展示的弹窗,以上述示例代码 modalMap.js为例, index页面的 modalList值就是 ['modal_1', 'modal_2', 'modal_3', 'modal_1_1', 'modal_1_2', 'modal_1_1_1', 'modal_1_1_2']
这里其实直接传弹窗数量就行了,index中有 7个弹窗可能同时展示,所以可以直接传 7,我这里之所以要传名称进去,实际上是为了方便调试,如果代码出问题了,比如页面上实际有 5个接口可以控制 5个弹窗的展示,但你却只订阅了 4次,如果只传数字,你就需要一个个找过去看是哪一个忘记订阅了,但如果传名称,你一下子就能调试出来,也就是代码的可维护性会好一点
当页面上任意一个弹窗的状态(即是否满足展示的条件)确定下来后,就进行订阅操作:
// 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('重复订阅') } } else { console.log('无效订阅') } }
this.modalFlatMap是为了记录订阅列表,当订阅列表的长度和 modalList相同时,说明所有的弹窗状态都已经准备就绪,可以根据这些弹窗的优先级进行展示了,也就是 notify方法要做的事情
notify方法中,先排除掉属性 show为 false的弹窗项,再对比剩下的弹窗的 level,只展示 level最大的那个弹窗:
// 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 // 这个 { level: -1 } 只是为了给 reduce函数一个 initialValue,modal项的 level都应该大于这个 initialValue的 level值,即 -1 }, { level: -1 }) highLevelModal.handler() } }
使用单例模式管理嵌套组件以及多个页面的弹窗
上述的 ModalManage类已经足以管理弹窗了,但还有个问题,如果一个页面上的弹窗,分散位于页面主组件及其子组件,甚至是子组件的子组件内,怎么办?
这个时候就需要使用单例了
// 单例管理 const manageTypeMap = {} // 获取单例 function createModalManage (type) { if (!manageTypeMap[type]) { manageTypeMap[type] = new ModalManage(getAllModalList(modalMap[type])) } return manageTypeMap[type] }
通过 createModalManage这个方法来创建 ModalManage实例,根据传入的 type来决定是否创建新的实例,如果单例管理对象 manageTypeMap中不存在 type对于的实例,则 new一个 ModalManage实例,存入 manageTypeMap中,并返回这个新实例,否则就返回 manageTypeMap中已经创建好了的实例
这样一来,无论弹窗分散在多少个组件内,无论这些组件嵌套得有多深,都能够在保证代码低耦合的前提下,顺利地订阅/发布事件
这里的 getAllModalList方法是个工具方法,用于从 modalMap中获取页面对应的弹窗数据结构:
// 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的参数type,其值可以就是一个字符串,例如如果需要管理首页 index上可能同时展示的所有的弹窗,则可以将 type 的值指定为 index,在 index主组件以及其包含弹窗的子组件内,都通过这个字段来获取 ModalManage单例对象:
const modalManage = createModalManage('index')