Published on
1767

React memo导致Antd表格固定列错位问题

Authors
  • avatar
    Name
    小辉辉
    Twitter

前言

工作中用到的前端UI库是在现有Antd库上进行二次定制开发的,常规的组件定制改动点不大,主要是相关样式,除了表格Table做了很多定制,这也很大程度上要感谢Antd table足够丰富的配置,我们之所以进行定制也是考虑到能够在大数据量的情况下可以拥有不错的性能,除此之外,我们的表格还需要支持编辑,这两大功能决定了表格这一块做了很多改动,后面就会提到这些改动在使用不当的情况下会带来的影响。

问题现象

不久前,有人向我反馈了一个表格组件的使用问题,他发现在配置右侧固定列时,会出现表格头和下方行数据错位的情况。

我问他要来了事例,发现要复现这个错位还有一个比较特殊的操作,具体在表格初次显示的时候,是没有错位的,但是界面上有个switch开关来控制这个表格的显隐,当开关打开时,表格会展示,开关关闭时,表格会隐藏,开关默认是开启的,这个情况就很奇怪,为什么经历一次显隐切换后,表格就错位了呢?

我看到这个第一反应是,会不会是他代码写的有问题?毕竟表格第一次能够正常展示的,就有可能是switch的切换后对表格列做了些什么特殊操作,于是决定看看他的项目代码。

一番查看后,比较失望,因为switch的操作真的就是做一个显隐控制state的操作,除此之外,再无其他,而且原来假设的列修改也排出了,因为列一直引用的是同一个数据。

这就很奇怪了,我和他说让我回去看看表格这一块的源代码再给他回复,因为这个现象有个很有趣的地方,就是它是怎么能做到只有第一次展示正常的?

定位原因

回去后,先是想着在本地复现下这个问题,我用了他的配置列数据,在本地启动了一个demo,运行起来后发现的确有问题,但是有一处不一样,我首次加载就错位了,咦,问题变得更加奇怪了。

既然有问题,那就开始定位排查吧,先看了先错位造成的原因,主要是固定列的单元格样式right属性设置不正确,默认全是0造成的,除此之外,和正常固定展示的列还少了些class类名,看到这里,我就在想为什么这些数据没有设置出来呢?

看了下自定义的表格,发现最后传给antd table的属性中,有一处异常的地方,这里要说明下,这个表格离我上次开发已经换了好多开发人员,所以有些代码我也没有多少印象了。

<Table
  components={{
    header: {cell: HeaderCell},
    body: {
      row: TableRow,
      cell: TableCell
    }
  }}
/>

上方的代码主要就是把Antd Table原有的单元格、行组件全部给替换成了自定义组件,因为上面排查到是数据行展示有问题,那我的第一反应就是注释上面body的配置,结果展示错位的问题就没有了。

到这里问题基本就定位到了,最后就发现上述的TableCell组件使用了React.memo导致了这次问题的产生。

这时候我们再来梳理下要解决的问题:

  • 表格展示错位

  • 表格第一次展示正常

  • 我们无法复现第一次正常的效果

再解决上述三个问题之前,我们先开看看React.memo的用法,官方的说明

memo 允许你的组件在 props 没有改变的情况下跳过重新渲染。

用法如下:

const MemoizedComponent = memo(SomeComponent, arePropsEqual?)

第一个参数是我们要优化的组件,第二个参数是一个比较函数,入参是旧pros和新props,返回值为true时表示组件无需重新渲染,返回false时表示组件需要重新渲染。

好了,让我们一一解答上面三个问题。

问题1:表格错位

上面提到过antd table固定列的实现需要借助于样式定位right值,这个值确保了列不会随着滚动条的移动2⃣️移动,那我们就可以假设单元格忽略掉了这些属性,通过memo返回true来达到,再去看看比较函数的实现,经过断点发现,果然,每次比较函数返回值都是true。

问题2:表格首次加载正常

这个问题我也是想了很久没有想明白,但最后也是通过调试来找到原因的,具体表现在首次加载好的环境没有走任何的memo比较函数,就这一点,完全就说明了,首次加载好的配置和我本地复现的应该有不一样的地方。

再仔细调试了下antd table的代码,就发现在row这个组件其实经历了两次不同的更新,分别是表格数据为空时,表格数据有值,所以我猜测,表格在数据为空时,已经计算好了每个固定列的定位值,在最后数据加载时直接做了一处更新即可。

问题3:本地无法复现问题2

我们只需要去验证问题2的数据是后设置上去的即可,果然,看了下开发环境上的代码,他的表格数据是在一次请求后再设置的,可以把它想象为组件内部的一个state状态,这就好说了,为啥经过switch开关切换后就显示不正常了,因为这部分数据已经存在组件内部了,所以这个效果就是我本地复现时的效果一样了。

解决方案

最后的办法当然是在memo比较函数里面加处理逻辑,因为memo的作用应该是当初特意用做性能优化的,这在最开始我就提到过,我们只需加一个标记操作列的配置项即可,这里我们用了className属性,约定好使用操作列时必须设置className包括table-col-operation,在里面加个includes判断,只要时操作列,返回true,一律执行更新,这样就能把antd table原有的cell属性给继承下来了。

总结

前端开发过程中遇到很多疑难问题时,我们首先还是要大胆假设,小心求证,借助于便利的调试工具,快速定位原因,当然对基础知识的掌握牢固程度也是一个不可缺少的前提。