【JS】写C端,如何优雅的处理多个弹框的显示?(附带源码)

前言

由于项目一开始没有做好规划或者说一开始就不是你维护的,导致首页的弹窗组件可能放了十多个甚至更多,不仅是首页有,首页内又引入了十多个个子组件,这些子组件内也有弹框,另外子组件的子组件也可能存在弹框,每个弹窗都有对应的一组控制显隐逻辑,但是你不可能让所有符合显示条件的弹窗都全都一下子在首页弹出来,如何有顺序的管理这些弹框是重中之重的事情

【JS】写C端,如何优雅的处理多个弹框的显示?(附带源码)

一个小场景

如下图:

【JS】写C端,如何优雅的处理多个弹框的显示?(附带源码)

小误区

【JS】写C端,如何优雅的处理多个弹框的显示?(附带源码)

带着这个业务痛点,我去踩坑了几种方案,下面来分享下以下这种配置化弹框方案(借鉴了动态表单的思路来实现

配置化弹框

// 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-弹框的唯一id

  • name: 弹框名称-可以根据名称很快找到该页面上的弹框

  • 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构造函数

// 上述弹框配置

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订阅

  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发布

  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

第一版代码

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验证一下

git clone [email protected]:vnues/modal-control.git

git checkout feature/first

yarn

yarn serve

第二版

接下来,我们来完善我们的弹框类ModalControl,我们先来分析下需要注意哪些问题吧

  • 可能存在多个接口控制弹框显示(比如A接口也可以调取这个弹框,后面持续迭代,B接口也可能调取这个弹框),所以不再是那种一对一的关系,而是多对一的关系,多个接口都可以控制这个弹框的显示,这里通过apiFlag来标识弹框,不再使用name

得益于我们的modalConfig配置,我们只需要补充一个apiFlag字段,便可以解决上述问题,是不是很方便,其实后续的复杂场景,也在这里补充字段完善就行

modalConfig

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验证一下

git clone [email protected]:vnues/modal-control.git

git checkout feature/second

yarn

yarn serve

待解决问题

这里我给出两种思路

  • 多个接口一起决定弹框是否展示,我们完全可以在接口层做合并,最终实现出来的效果就是一对一

  • 订阅方法做去重,利用高阶函数再次封装对应的handler实现多个接口一起决定弹框是否展示,个人还是推荐第一种解决方案

前端学习笔记📒

  • 我的前端学习笔记📒
  • 我的简历模板

【JS】写C端,如何优雅的处理多个弹框的显示?(附带源码)

最后

1.看到这里了就点个在看支持下吧,你的「点赞,在看」是我创作的动力。

2.关注公众号前端壹栈,回复「1」加入前端交流群!「在这里有好多前端开发者,会讨论前端知识,互相学习」!

3.也可添加公众号【前端壹栈】,一起成长

【JS】写C端,如何优雅的处理多个弹框的显示?(附带源码)

以上是 【JS】写C端,如何优雅的处理多个弹框的显示?(附带源码) 的全部内容, 来源链接: utcz.com/a/92308.html

回到顶部