Published on
1573

Antd 页签组件 Tab 下的日期组件 Datepicker 导致的异常滚动行为

Authors
  • avatar
    Name
    小辉辉
    Twitter

复盘

先是交代下相关场景:

我们有个页面下面包括了一些表单组件例如input输入框,select下拉框,Datepicker 日期组件,这些表单组件为了能够在不同屏幕下展示合理,我们还做了一个自适应的操作,类似于屏幕尺寸在1000px下,一个行显示两个表单组件,1300px下一行显示三个表单组件,1300px以上一行展示四个表单组件。

下面开始讲下这个自适应的实现逻辑,加了一个窗口大小监听事件,这个事件里面根据规则确定每个表单组件外部容器的类名,每个组件类名控制着宽度,以百分比来实现,这样以来,随着屏幕大小的改变,这个自适应功能完美的实现了。

直到一次出现了异常情况,那就是将上面的这个表单组件放到页签下面时,刚刚打开时正常的,但是切换页签后,再回到第一个页签后,会意外发现底部出现了多余的水平滚动条。

我刚刚看到这个现象的时候,直接懵了,为啥还和页签切换有关系呢?但是以往多次的水平滚动条经验告诉我肯定是有元素在页签切换后超长了,一步步一个个元素查看下去,终于定位到是 Datepicker 这个日期选择组件,下面有个popover的元素宽度在切换后突然变成了800px,要知道我是正常1920xp的屏幕,按照正常情况这个日期组件应该只有四分之一的大小,差不多就是500px,800px的元素,难怪会在外层出现滚动条了。

要说怎么解决,其实暴力方案只需要通过在表单的外部容器添加一个overflow: hidden样式即可。

但是我觉得还是有必要对这个问题一探究竟,毕竟这种直接隐藏的操作也只是权宜之计,后面如果遇到了不能隐藏的场景还是得来解决这个问题,还不如试试自己能不能找到背后的原因吧。

回到问题上面,为什么第一次打开的时候显示正常,页签操作了以后不对了。

我看了下刚刚打开的时候,日期组件popover的宽度差不多和外部的容器一样,也就是400px的样子,那怎么就突然变成了800px了呢?

于是,先看了下页签切换一次的情况,发现这个时候popover的宽度会变为0,这个也好理解,毕竟外部容器被隐藏了,再切回来就变成800px了。

这时候只能打断点调试了,给这个popover元素加了dom属性修改断点,就发现了在切换回来过程中是有个resize事件给popover元素赋值的,这个resize事件的绑定来源是tab页签组件,简单理解就是页签监听到元素尺寸变化后,会重新渲染下下面的组件,也就是这里的 Datepicker 组件,在 Datepicker 组件内部也看到了popover元素宽度的计算方法,居然和我想象的完全不一样,antd内部的实现是直接读取外部容器的scrollwidth值,来作为popover元素的宽度,显然,这个800p就是在某个时期日期组件的实际宽度,可是为什么是800px呢?

再通过断点可以看到,800px对应的表单容器宽度是默认的50%,也就是初始化的类名,这个初始化类名可以理解为表单容器内部的resize事件在监听到页签切换导致容器宽度变0给赋值上去的。在切换回来时,由于还是默认的50%宽度,所以日期组件就获取了这个宽度来作为默认值。

那么另一个问题来了,我们的表单组件也是有resize事件的啊,通过resize事件会给表单赋值一个正确的类名,这也是我们一开始说到了表单组件能够自适应屏幕尺寸,照理说设置了类名以后,这里对应的是四分之一的类名,Datepicker 内部应该能过读取到四分之一的宽度,再调整一次才对啊?

可是当我打完断点之后惊奇的发现表单组件resize事件导致的更新,在日期组件内部取到的外部容器类名还是上次的?

嗯?奇不奇怪?明明我reszie更新了外部容器的类名啊,怎么内部的日期组件读取到的不对呢?而且等断点跳过后,我发现外部的表单容器的类名随之也生效了,这个现象说明了什么?

按照这个现象,我大胆的猜测,父组件更新会导致子组件的更新,但是父组件类名的更新,会晚于子组件的更新,只要这样才能解释的通了,为了验证这个,我给表单容器的元素打了dom属性变更断点,果然进行了验证,这样一来,我们对整个问题的来源就清晰了,造成这个问题还是和react背后的更新原理有很大的关系,当然也和antd日期时间组件的实现实时读取容器宽度有很大的关系。

那怎么正确的解决这个问题呢?既然现在的问题是类名更新延迟,那我们只要确保让日期组件能够正常获取到更新后的类名即可,通过记录前后两次的表单容器类名,如果不一致,我们只需要重新触发一次强制更新即可解决,这样日期组件内部便能读取到最终外部容器的宽度了。

好了,到这里这个异常问题终于得以解决,而由此引出的react背后的组件属性更新顺序这个问题,后面看有没有时间,再出一篇文章单独记录吧。