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

3次阅读

共计 7498 个字符,预计需要花费 19 分钟才能阅读完成。

前言

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

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

一个小场景

下面这么剖析可能有同学还是不理解这个 业务痛点 ,咱们举个例子,假如首页页面有个 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. 也可增加公众号【前端壹栈】,一起成长

正文完
 0