跟混乱的页面弹窗说再见


Posted in Javascript onApril 11, 2019

对于一些快速迭代的产品来说,特别是移动端 C端产品,基于用户运营的目的,在 app首页给用户展示各种各样的弹窗是很常见的事情,在产品初期,由于迭代版本和运营策略变化地还不是太大,所以可能觉得没什么,但当产品运营到后期,各种八竿子打不着的运营策略轮番上阵,弹窗的样式、逻辑等都变了不知道多少遍的时候,问题就出来了

跟混乱的页面弹窗说再见

由于前期没有做好规划,首页的弹窗组件可能放了十多个甚至更多,不仅是首页有,首页内又引入了十多个个子组件,这些子组件内也有,搞不好这些子组件内还有子组件,子组件的子组件同样还有弹窗,每个弹窗都有对应的一组控制显隐逻辑,分散在多个组件多个方法中,但是首页只有一个页面,你不可能让所有符合显示条件的弹窗,全都一下子弹出来,反正我是没见过这么做的 app,那么如何管理这些弹窗就成了头等大事

而往往当你意识到这一点的时候,很可能也正是局势发展到无法控制的时候 治不了,等死吧

场景:A弹窗和 B弹窗位于主组件内,C弹窗位于主组件的子组件 C中,D弹窗位于主组件的子组件 B中,E弹窗位于主组件的子组件F的子组件G中

 跟混乱的页面弹窗说再见

PM:我希望刚进入这个页面的时候,只有当 A弹窗 和 B弹窗以及 C弹窗,都不展示的时候,才展示 D弹窗,如果 D弹窗展示过了,除非 B弹窗之后又展示了一遍,否则无论什么情况下都不展示 E弹窗
FE:???

 稍加思考一下,其实这件事情并不难办,交给后端通过接口控制所有弹窗的显隐就行了 主要是架构的提前规划,以及低耦合的代码逻辑

弹窗的配置化

先确定一个大体思路,首先,必须要明确地知道当前页面共有哪些弹窗组件,包括页面的子组件以及子组件的子组件内的弹窗组件,这是必须的,否则你连有哪些组件都不知道怎么精确控制?
所以,还是上面那句话,提前规划,防患于未然是很重要的,不然等页面迭代了几十版,当初写代码的人都不在了你才想到去统计一下页面上到底有多少个弹窗,那真是够你喝一壶的

那么就需要在一个地方统一把这些弹窗全记录下来,方便管理,于是可以得到下面这种数据结构:

// modalMap.js
export default {
 // 记录首页 index页面内的弹窗项
 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
      }]
     }
    }
   }
  }
 }
 // ...还可以继续记录其他页面的弹窗结构
}

modalMap.js文件记录每个页面内所有的弹窗项,例如,首页 index内的弹窗项都在属性名index对于的值数据结构中,index这个页面主组件内存在两个 modal,可以分别命名为 modal_1和 modal_2,如果 index这个页面主组件的子组件内也有 modal,则继续嵌套,例如,index主组件的子组件 child1中也有 modal,那么就把 child1放到 index的 children中继续记录,以此类推

这种结构看起来比较清晰,主组件及主组件内的子组件内的 modal都很清晰,一目了然,当然,你可以不用这种结构,完全取决于你,这里就暂时这么定义

每个 modal除了 name之外,还有 level 和 show属性

level 用于标识当前 modal的层级,每个页面正常只能同时展示一个 modal,但如果有多个 modal都同一时间都满足展示的条件,则对比它们的 level值,哪个大就优先展示哪个,其余的忽略掉,杜绝一个页面可能提示展示多个弹窗的情况;

show属性则是在 modal内部来决定 modal最终是否展示,这样一来就可以无视外界条件,很轻松地通过配置来禁止掉弹窗的显示

通过发布/订阅模式来管理弹窗

弹窗的配置结构已经确定了,下一步就是对这些配置的管理了

一般情况下,多个页面同时满足条件需要进行展示的场景,大多数都是发生在刚进入页面,页面发出多个请求,这些请求的返回结果分别控制对应的一个弹窗的展示

因为发出去的这些请求很可能分属于不同的业务线或部门管辖,相互独立,所以说如果把弹窗的控制权交给后端来做,其实是有点困难的,再加上请求是异步的,前端想要用意大利面条式代码来保证弹窗之间的互斥性也不太容易,综合起来,也就导致了当页面上迭代出了数十个以上弹窗的时候,如果没有提前规划好,还是很容易出现弹窗同时展示的问题的

这里暂时就以刚进入页面的情况为例,进行逻辑梳理

首先,我需要知道页面上有哪些弹窗可能会在刚进入页面的时候弹出来(即通过接口控制单个弹窗的展现与否),然后在所有弹窗的数据都拿到了的时候(即跟弹窗相关的接口都已经返回数据),才进行弹窗的展示

这种情况比较适合使用发布/订阅者模式,单个接口的数据返回就是一个订阅,当所有接口都订阅之后,就进行发布,也就是弹窗展示

// 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')

这样做同时也解决了另外一个问题,就是多个页面的弹窗管理问题,index页面通过 index创建 ModalManage单例,详情页就可以通过 detail来创建 ModalManage单例,双方互不干扰
总结

上述所有示例代码已经上传到 github,有兴趣地可以看下

本文只是对弹窗这么一种具体的案例进行分析,实际上应用于其他场景,例如页面同一个位置的悬浮挂件管理等都是可行的
无论是弹窗的管理还是挂件的管理,放在 mvvm框架中,都是数据的管理,主流前端框架对于复杂的数据管理,都已经有对应的解决方案,例如 vuex 和 redux等,这些解决方案当然也能够解决上面的问题

本文主要是对这种理念的探讨,探讨出一种通用的解决方案,无论你用的是 vue、react、angular还是jquery一把梭,亦或是微信小程序、支付宝小程序、快应用等,都可以低成本地轻松套入使用

以上所述是小编给大家介绍的跟混乱的页面弹窗说再见详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
Javascript中eval函数的使用方法与示例
Apr 09 Javascript
原生js获取宽高与jquery获取宽高的方法关系对比
Apr 04 Javascript
javascript event在FF和IE的兼容传参心得(绝对好用)
Jul 10 Javascript
JS模拟键盘打字效果的方法
Aug 05 Javascript
easyui datagrid 大数据加载效率慢,优化解决方法(推荐)
Nov 09 Javascript
VUE axios发送跨域请求需要注意的问题
Jul 06 Javascript
js用类封装pop弹窗组件
Oct 08 Javascript
JS获取一个表单字段中多条数据并转化为json格式
Oct 17 Javascript
微信小程序实现滚动消息通知
Feb 02 Javascript
详解javascript appendChild()的完整功能
Aug 18 Javascript
解决angularjs service中依赖注入$scope报错的问题
Oct 02 Javascript
Vue实现简单计算器案例
Feb 25 Javascript
vue实现todolist功能、todolist组件拆分及todolist的删除功能
Apr 11 #Javascript
vue实现todolist基本功能以及数据存储功能实例详解
Apr 11 #Javascript
JavaScript高阶教程之“==”隐藏下的类型转换
Apr 11 #Javascript
使用Vue父子组件通信实现todolist的功能示例代码
Apr 11 #Javascript
详解jQuery设置内容和属性
Apr 11 #jQuery
js作用域和作用域链及预解析
Apr 11 #Javascript
关于js陀螺仪的理解分析
Apr 11 #Javascript
You might like
十天学会php(1)
2006/10/09 PHP
PHP使用DOM和simplexml读取xml文档的方法示例
2017/02/08 PHP
js+FSO遍历文件夹下文件并显示
2007/03/07 Javascript
JavaScript中的匀速运动和变速(缓冲)运动详细介绍
2012/11/11 Javascript
AspNet中使用JQuery上传插件Uploadify详解
2015/05/20 Javascript
jQuery轻松实现表格的隔行变色和点击行变色的实例代码
2016/05/09 Javascript
AngularJS基础 ng-mouseover 指令简单示例
2016/08/02 Javascript
vue 实现左右拖拽元素并且不超过他的父元素的宽度
2018/11/30 Javascript
JS数组求和的常用方法实例小结
2019/01/07 Javascript
在Vue环境下利用worker运行interval计时器的步骤
2019/08/01 Javascript
JavaScript中变量提升机制示例详解
2019/12/27 Javascript
[57:29]Alliance vs KG 2019国际邀请赛小组赛 BO2 第二场 8.16
2019/08/17 DOTA
用Python写一个无界面的2048小游戏
2016/05/24 Python
详解python的webrtc库实现语音端点检测
2017/05/31 Python
浅谈DataFrame和SparkSql取值误区
2018/06/09 Python
python3.6+selenium实现操作Frame中的页面元素
2019/07/16 Python
Python如何把多个PDF文件合并代码实例
2020/02/13 Python
pycharm设置python文件模板信息过程图解
2020/03/10 Python
详解如何将 Canvas 绘制过程转为视频
2021/01/25 HTML / CSS
美国休闲服装品牌:J.Crew Factory
2017/03/04 全球购物
Liu Jo西班牙官网:意大利服装品牌
2019/09/11 全球购物
俄罗斯马克西多姆家居用品网上商店:Максидом
2020/02/06 全球购物
Ruby中的保护方法和私有方法与一般面向对象程序设计语言的一样吗
2013/05/01 面试题
硕士研究生个人求职信
2013/12/04 职场文书
激情洋溢的毕业生就业求职信
2014/03/15 职场文书
演讲比赛策划方案
2014/06/11 职场文书
社区班子对照检查材料
2014/08/27 职场文书
单位在职证明书
2014/09/11 职场文书
2014迎国庆演讲稿
2014/09/19 职场文书
聘任通知书
2015/09/21 职场文书
如何计划开一家便利店?
2019/07/31 职场文书
vue组件的路由高亮问题解决方法
2021/05/11 Vue.js
36个正则表达式(开发效率提高80%)
2021/11/17 Javascript
postman中form-data、x-www-form-urlencoded、raw、binary的区别介绍
2022/01/18 HTML / CSS
关于vue-router-link选择样式设置
2022/04/30 Vue.js
详解Spring Security如何在权限中使用通配符
2022/06/28 Java/Android