关于前端:写C端如何优雅的处理多个弹框的显示附带源码

前言

最近写的挪动端业务常常跟弹框打交道,偶然解决对于多个弹框的显示问题也是顾此失彼,特地是产品常常改需要,那么有没有一种优雅的解决方案去解决下面这种问题,或者说,淘宝拼多多等是怎么解决这种问题的

因为我的项目一开始没有做好布局或者说一开始就不是你保护的,导致首页的弹窗组件可能放了十多个甚至更多,不仅是首页有,首页内又引入了十多个个子组件,这些子组件内也有弹框,另外子组件的子组件也可能存在弹框,每个弹窗都有对应的一组管制显隐逻辑,然而你不可能让所有合乎显示条件的弹窗都全都一下子在首页弹出来,如何有程序的治理这些弹框是重中之重的事件

一个小场景

下面这么剖析可能有同学还是不理解这个业务痛点,咱们举个例子,假如首页页面有个A组件,A组件有一个弹框A_Modal须要在关上首页显示进去,enen…很简略,咱们依照平时的逻辑申请后端接口拿到数据去管制弹框显示就行,咱们持续接着迭代,此时遇到了一个B组件,同样也是要显示在首页因为是新流动,所以优先级比拟大须要显示B_Modal弹框,这时候你可能要去找找管制A组件的接口找到后端说这个组件不显示了或者说本人手动重置为false,一个组件能够这样搞,然而几十个呢?,不太事实

如下图:

这些弹框是都要在首页上显示的弹框

小误区

❗️留神以下这种交互弹框不在咱们探讨范畴之内,比方通过按钮弹出弹框这种,像这类弹框通过交互事件咱们管制就行,咱们要解决的弹框场景是通过后端接口来显示弹框,所以前面咱们所说的弹框都是这种状况,留神即可

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

配置化弹框

之前写治理后盾零碎的时候有理解过动静表单,理论就是通过一串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-弹框的惟一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构造函数

依据以上思路,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实现多个接口一起决定弹框是否展现,集体还是举荐第一种解决方案

前端学习笔记????

最近花了点工夫把笔记整顿到语雀上了,不便同学们浏览:公众号回复笔记或者简历

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

最初

1.看到这里了就点个在看反对下吧,你的「点赞,在看」是我创作的能源。

2.关注公众号前端壹栈,回复「1」退出前端交换群!「在这里有好多前端开发者,会探讨前端常识,互相学习」!

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理