一个开源组件 bug 引发的分析
这是一个悲伤的故事。某日清晨,距离版本转测还剩一天,切图仔的我正按照计划有条不紊的画页面。当我点击一个下拉弹框组件中分页组件页数过多而出现的向后 5 页省略号时,悲剧开始了,弹框被收回了。情景再现

问题
问题的表象很简单,使用的是组件库的下拉弹窗组件,在组件中使用到了分页组件,当点击分页组件的向后 5 页快速跳转时,弹窗被收回了。我们的预期是能够继续操作的,只有点击弹框外部时,弹窗才会被收回。
分析
发现这个问题我做了如下分析:
确定这是一个问题
再次重复操作问题,确定问题出现的条件,能够在特定条件下复现的问题才是问题。我稳定的复现了问题条件是:分页组件出现向前跳转 5 页或向后跳转 5 页,点击到不会再出现向前跳转 5 页或向后跳转 5 页这样的快速跳转后,弹框会被收回。确定是自己组件配置使用问题还是组件本身 bug
我在组件的文档中并没有看到有关这个问题的特别说明。接着我又在官方提供的代码运行环境中,写了一个使用的 demo(实际项目中的代码复杂度较高,可能存在被其他样式等因素影响而产生问题,使用功能单一的 demo 有助于我们快速确定问题范围,且官方运行环境一般使用的是其最新的组件版本),发现与我在项目中使用存在同样问题。到这里可以排除是组件配置使用,组件库版本不是最新,及项目环境影响导致的问题。在组件库的开源项目中查询 issue
在 issue 中搜索关键字查询是否有相关 issue,如果运气好找到相关问题,一般会有相关的讨论,就算没有具体的解决措施也能让你明白问题大概在哪里。我运气比较差,没有找到相关问题的 issue,所以只能自己提一个 issue 了。有时间有耐心等待维护人解决你提的 issue,问题分析到这里就可以结束了。查看组件源码
显然我的时间很紧急,问题必须尽快解决,且我也想确定一下问题具体在哪里,看自己能否解决。很快我找到的相关组件的源码,经过定位发现,弹框下拉组件中使用了 v-click-outside 指令,用来触发点击指令之外的元素收起弹框。
进一步深入源码,发现 v-click-outside 组件的原理,是在 document 组件上注册了一个 click 监听事件,当有点击事件发生,监听函数会判断,点击事件发生的元素,是否包含在使用指令的元素中。如果是监听函数返回,否则触发指令绑定的事件(在弹框组件中就是隐藏弹框)。代码片段如下
// el 表示指令绑定的元素,event为点击事件
const isClickOutside = event.target !== el && !el.contains(event.target)
if (!isClickOutside) {
return
}
if (middleware(event, el)) {
handler(event, el)
}
- 断点调试
经过上面的步骤过后,我还没有发现问题的所在,所以只能打断点进行代码调试了。经过调试发现问题出在 el.contains(event.target) 这一段,分页组件中的向前 5 页元素,点击触发后就被 v-if 指令从页面中移除了。所以运行到这一句时,el 中是不包含向前 5 页元素的。也就是说这里向前 5 页元素被错误的判断为不在 el 元素中,指令绑定的弹框收起函数被触发,弹框被收回了。
解决问题
问题已经找出,现在问题是怎么解决,因为问题是出在组件中,但是我们又不能直接修改组件库代码,直接修改项目依赖代码,不利于代码维护,也治标不治本。最好是能在项目代码中添加代码解决这个问题。
开始时走了一些弯路,想着用伪元素去覆盖,被点击的元素,试图去混淆 e.target,让 el.contains(event.target) 为true。这样确实取得了一些成效,但是在页数更多了以后依然有问题,而且我也不是很清楚这样用伪元素遮挡可以让事件触发元素不是本来元素的原理是什么。更重要的一点是,写这篇文章的时候我完全回忆不出当时我为什么会想到这个方案,可能是灵感吧,解决问题真的很需要灵感。
在使用了弯路解决办法暂时解决问题后,我开始思考更彻底的解决方案。归根结底问题是出在了 v-click-outside 指令上,如果我够找到一种正确的判断方式,让被删除的元素也可以被判断为在指令注册元素中,那么问题将得到彻底解决。经过不断的尝试与思考,我发现将点击事件注册在捕获阶段触发时,得到的指令绑定元素中依然有应该被删除的点击元素。到这里问题基本就明朗了,彻底的解决方案也就出现了。
由于组件引入的 v-click-outside 指令是局部注册在下拉弹框组件上的,所以我使用了 vue 的extends 继承了组件的弹框下拉组件。在继承的组件中重新注册了指令 v-click-outside,该指令注册在 document 上的点击事件是捕获阶段触发的。
由于是继承覆盖组件库中的组件,所以组件库升级不会带来太大的影响,同时也不用重新写一个组件,减少了工作量。到这里问题就得到了彻底的解决。
输出 v-click-out 包
解决完问题之后,我在 npm 上搜索了一些 click-outside 相关的包,发现这些包中注册在 document 上的点击事件,普遍是在冒泡阶段触发的,也就是说都存在文中我所遇到的问题。于是经过几天业余时间的努力,我开源了一个基于 vue 的 click-outside 指令开源项目catch-click-outside(不要脸求star)
一点思考
在之前的工作中,我也解决了不少问题,这次之所以会记录解决过程,一个是因为这个问题我产生了一些输出,可能对别人会有些微的帮助。另一个比较重要的原因是,在解决问题过程中走了一些弯路,也遇到了一些坎,但是这些问题都在不停的思考与事件中逐渐清晰,然后被解决,我觉得这个过程很值得记录。文字功底有限,这个解决过程记录的平淡无奇,谨以此作为备忘。
转载请注明出处!
一个开源组件 bug 引发的分析的更多相关文章
- android菜鸟学习笔记25----与服务器端交互(二)解析服务端返回的json数据及使用一个开源组件请求服务端数据
补充:关于PHP服务端可能出现的问题: 如果你刚好也像我一样,用php实现的服务端程序,采用的是apache服务器,那么虚拟主机的配置可能会影响到android应用的调试!! 在android应用中访 ...
- Android自定义控件 开源组件SlidingMenu的项目集成
在实际项目开发中,定制一个菜单,能让用户得到更好的用户体验,诚然菜单的样式各种各样,但是有一种菜单——滑动菜单,是被众多应用广泛使用的.关于这种滑动菜单的实现,我在前面的博文中也介绍了如何自定义去实现 ...
- .net 开源组件推荐 之 StackExchange
已经两年没更新过博客了!!! StackExchange,地址:https://github.com/StackExchange,开源的这些项目都是在StackOverflow线上使用的. 说起Sta ...
- z-index失效原因分析——由一个bug引发的对层叠上下文和z-index属性的深度思考
新年刚开工就被一个bug虐得整个人都不好了,特地记录下. (一)bug描述 在一个fixed-data-table(一个React组件)制作的表格中,需要给表头的字段提示的特效,所以做了一个提示层,但 ...
- FluentConsole是一个托管在github的C#开源组件
FluentConsole是一个托管在github的C#开源组件 阅读目录 1.控制台能有啥滑头? 2.FluentConsole基本介绍 3.使用介绍 4.资源 从该系列的第一篇文章 .NET平台开 ...
- 由一个bug引发的SQLite缓存一致性探索
问题 我们在生产环境中使用SQLite时中发现建表报“table xxx already exists”错误,但DB文件中并没有该表.后面才发现这个是SQLite在实现过程中的一个bug,而这个bug ...
- 开源组件ExcelReport 1.5.2 使用手册
ExcelReport是一款基于NPOI开发的报表引擎组件.它基于关注点分离的理念,将数据与样式.格式分离.让模板承载样式.格式等NPOI不怎么擅长且实现繁琐的信息,结合NPOI对数据的处理的优点将E ...
- .net 开源组件
文章转自:http://www.cnblogs.com/asxinyu/p/dotnet_opensource_project_3.html 在前2篇文章这些.NET开源项目你知道吗?让.NET开 ...
- 一个MySQL-JDBC驱动bug引起的血案……
问题背景 公司是做电商系统的,整个系统搭建在华为云上.系统设计的时候,考虑到后续的用户和订单数量比较大,需要使用一些大数据库的组件.关系型数据库这块,考虑到后续数据量的快速增长,不是直接写入MySQL ...
随机推荐
- python爬虫添加请求头和请求主体
添加头部信息有两种方法 1.通过添加urllib.request.Request中的headers参数 #先把要用到的信息放到一个字典中 headers = {} headers['User-Agen ...
- gitbook 入门教程之从零到壹发布自己的插件
什么是插件 Gitbook 插件是扩展 Gitbook 功能的最佳方式,如果 Gitbook 没有想要的功能或者说网络上也没有现成的解决方案时,那么只剩下自食其力这条道路,让我们一起来自力更生开发插件 ...
- python win32com 读取带密码的excel
之前用到的win32com读取带密码excel的相关内容,今天刚好准备整理下,突然发现方法又不灵了. 以下为错误示范: # 已知excel密码去除 def del_password(filename, ...
- web前端之浏览器: 知识汇总
一.URL到页面 准备阶段: 输入URL,Enter进入查找 浏览器在本地查找host文件,匹配对应的IP: 找到返回浏览器并缓存 没有,则进入路由查找: 找到返回浏览器并缓存 再没有,再进入公网DN ...
- mysql 生成数据字典sql语句
SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, COLUMN_COMMENT FROM information_schema. C ...
- 【Java】后台将文件上传至远程服务器
问题:由于系统在局域网(能访问外网)内,但外网无法请求局域网内服务器文件和进行处理文件. 解决:建立文件服务器,用于存储文件及外网调用. 客户端(文件上传): package cn.hkwl.lm.u ...
- spring5 源码深度解析----- Spring事务 是怎么通过AOP实现的?(100%理解Spring事务)
此篇文章需要有SpringAOP基础,知道AOP底层原理可以更好的理解Spring的事务处理. 自定义标签 对于Spring中事务功能的代码分析,我们首先从配置文件开始人手,在配置文件中有这样一个配置 ...
- guava缓存批量获取的一个坑
摘要 Guava Cache是Google开源的Java工具集库Guava里的一款缓存工具,一直觉得使用起来比较简单,没想到这次居然还踩了一个坑 背景 功能需求抽象出来很简单,就是将数据库的查询sth ...
- vue父子组件钩子函数的执行顺序
加载渲染过程 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount-& ...
- Jmeter Json List Element Assertion使用详解
使用背景: jmeter4.0本身提供json Assertion断言,但当我们想要对返回的json list中的多个字段进行断言的时候,我们就会感到很无力.那么此时我们就可以通过Json List ...