一、背景
最近公司安排我去审查公司组件库,我们公司的组件库是基于antd 3.x做的二次封装。我在审查同事开发的分页器的时候,发现了几个bug。经过排查后发现,这个bug是因为antd引用第三方组件rc-pagination就有的bug,因此,我在github还给antd提了两个issue,一个被确认标记为了bug,另一个被关闭了,其实本质这两个issue的是一样的bug。值得一提的,在antd4.x和3.x都存在这个bug。
二、bug复现
这个bug主要是分页器快速跳页与正常直接选取页码跳转之间的冲突。
复现:
1、选中当前任何一个页码(包括第一页和最后一页)
2、选中快速跳页,输入任何一个不是当前页码的数字,注意:这里要保证快速跳页不失去焦点
3、点击除了当前页码和快速跳页之间的任意一个页码(包括上一页和下一页)
4、那么就会出现连续两次跳页的情况
特殊情况:
1、如果快速跳页页码与当前已经选中的页码相差为2,并且此刻我们要去点击的那个页码在二者中间,那么就会出现我们要点击的页码和快速跳页的页码都被选中的情况。
2、如果此时已经被选中的页码的为第一页或者最后一页,那么上一页或者下一页的按钮应该为禁用状态。点击这两个按钮不应该生效,但是依旧会产生跳页两次的情况
三、分析原因
1、分页会连续跳页两次原因
快速跳页绑定了两个事件,一个是失去焦点的事件,一个是回车事件。这两个事件都会触发快速跳页。而在点击选择页码的时候,绑定的则是点击事件。回车事件与这个bug没有关系。
当你在快速跳页输入一个其他的页码的时候,正常在这个input之外,随便点击一下,这个失去焦点的事件就会被触发。那么问题就来,如果我还在没有失去焦点的时候,不希望快速跳页了,期望直接选择页码,那么,如果你这样做了,就会产生连续跳页的情况。
然后我们会发现,失去焦点的事件触发的时间在点击事件之前。这也是为什么会产生这个bug的原因之一。
2、分析为什么会出现同时选中两个页码的情况出现
不知道,虽然我解决了,但是这里是真没排查出来为什么不是跳页,而是同时选中b,要是有知道的小伙伴们,希望不吝赐教。
3、分析为什么在上一页和下一页按钮禁用的时候,点击依旧会出现两次跳页的情况
这是因为失去焦点的事件在点击事件之前,我们在禁用状态下点击上一页和下一页的时候,虽然此时是禁用状态,但是我 先触发的是失去焦点事件,当我们点击事件生效的时候,此时的上一页和下一页的状态已经被改变了,只是我们没有看到。
四、解决方法
目的:我们的目的是要阻止在点击具体页码或者上一页和下一页的时候触发失去焦点事件。
思路:我们在blur事件的时候的event对象只会是那个input,因次我们在全局绑定了一个mousemove事件,获取鼠标在失去焦点的时候所处的元素,并将其绑定到useRef上,然后我们在blur事件处理函数中获取最外层的分页按钮容器。封装一个函数,判断blur的时候的鼠标所在的元素是否是分页按钮容器的子元素。如果是,那blur事件就不触发,如果不是,就会触发。
const ref=useRef(null);
const handleMouseMove=useCallback((e)=>{
ref.current=e.target;
},[])
useEffect(() => {
document.addEventListener('mousemove',handleMouseMove);
return ()=>{
document.removeEventListener('mousemove',handleMouseMove);
}
}, [])
判断一个元素是否是另一个元素的子节点:
function isDOMContains(parentEle,ele){
//parentEle: 要判断节点的父级节点
//ele:要判断的子节点
//如果parentEle h和ele传的值一样,那么两个节点相同
if(parentEle == ele){
return true
}
if(!ele || !ele.nodeType || ele.nodeType != 1){
return false;
}
//如果浏览器支持contains
if(parentEle.contains){
return parentEle.contains(ele)
}
//火狐支持
if(parentEle.compareDocumentPosition){
return !!(parentEle.compareDocumentPosition(ele)&16);
}
//获取ele的父节点
var parEle = ele.parentNode;
while(parEle){
if(parEle == parentEle){
return true;
}
parEle = parEle.parentNode;
}
return false;
}
处理blur函数:
const inputHandle = (e) => {
const jumperEl=document.querySelector(".cw-pagination-input-main");
const paginationEl=document.querySelector('.cw-pagination-wrapper');
if(isDOMContains(jumperEl,ref.current) || isDOMContains(paginationEl,ref.current)){
return;
}
checkJumperValue(e.target.value)
}