前言
今天产品反馈表格样式在低分辨率屏幕下样式错位问题
那太简单了,给antd table
加上scroll
属性不就行了?并且给指定Column
加上width
,于是:
jsx
<Table
dataSource={list}
loading={loading}
rowKey={(record) => record.id}
pagination={false}
size="middle"
scroll={{
x: 1200
}}
>
<Table.Column
dataIndex="aaa"
width={120}
title="aaa"
/>
</Table>
但运行之后给我报了这个错
这个是浏览器提供的 JavaScript API
,允许开发者监视 DOM
元素的大小变化。
对于动态生成的表格、图表或其他复杂的交互式元素,ResizeObserver
可以帮助监测容器大小的变化,以便调整表格列宽、图表尺寸或者重新渲染数据。这是它在Antd中的作用。
先不管,去github issue
看看啥情况,还真有几条:
点进去看了并没有一个完美的解决方案,同时我从中get到了一个信息,这个错误是由于rc-table
导致的
于是我产生产生了疑问:什么是rc-table
?antd table
跟它有什么关系?
我重新去MDN
看了这个API的说明,它还给出相关错误解释:
developer.mozilla.org/en-US/docs/...
上面的代码表示如果循环修改元素的width
,ResizeObserver
在计算尺寸的同时,节点的宽度依旧发生变化,浏览器不允许你这样操作,导致抛出这个错误。
带着这些已知信息,我决定翻一翻antd
源码看看。
Vs Code调试
在React
项目中创建launch.json
,通过launch
启动3000
端口的调试服务
json
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "针对 localhost 启动 Chrome",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
}
]
}
我使用的是class
组件来引入table
组件,要知道antd
组件是函数式组件,它们在解析jsx
是不一样的,class
组件通过updateClassComponent
来创建DOM,在render
中打个断点试试:
通过调用栈就可以看到是通过beginWork
来调度不同类型的组件方法,点进去看看:
可以看到这里是通过Switch...Case
来做分支,要想看到table
组件的执行过程,找到updateFunctionComponent
中的renderWithHooks
方法有这段代码:
所有的函数式组件都会在这里处理,打个断点试试:
页面中这么多组件,怎么找到Table
呢,一个个点下去太慢了,我们通过条件断点过滤一下:
释放断点,可以看到就在ant table
组件停下来:
点击单步调试进入子函数中,来到的就是table
组件编译之后的源码中
这个文件中,的确看到了之前说的rc-table
,ant table
是基于这个table
扩展的,最后创建的就是RcTable
这个组件
找到这个包下面的es/Table.js
文件,打一个断点
可以看到的确是table
传过来的Props
,默认dataSource
为[]
, 往下面可以看到,在这里创建Table Body
节点的:
进入body
文件,可以看到这里是根据props
传递的列表数据长度来创建行:
而初始化是dataSource
为空,进入创建ExpandedRow
组件
可以看到,这里进行创建真实节点,绑定的style
中width
的宽度是动态的,这就有可能造成上面的问题。
现在问题是从哪里监听元素变化的呢?这就要回到rc-table
中的代码了:
重新进行断点,horizonScroll
为true
说明我们表格的确是横向滚动的
这里会依赖rc-resize-observer
包中的组件ResizeObserver
,最终会去到observerUtil.js
工具方法中,这里定义了监听逻辑
再来验证一下,我监听DIV节点,看是否可能进入到这里
可以看到,这里捕获了DIV节点,正是Body
部分。
到这里,基本上就破案例~
那是不是意味着,初始化的时候只要列表不为空,就不会有问题呢?我们来试试,默认传递[{}]
还真是这样,逛了一下网上的确有这种做法。
这个问题的原因是频繁改变节点的样式,导致监听器出问题,如果真是这样的话,那是不是对监听器回调进行拦截,通过防抖或者用requestAnimationFrame
包裹也可以解决这个问题?比如这样:
js
const divElem = document.querySelector("body > div");
const debounce = () => {}
const resizeObserver = new ResizeObserver(debounce(entries) => {
for (const entry of entries) {
entry.target.style.width = entry.contentBoxSize[0].inlineSize + 10 + "px";
}
});
window.addEventListener("error", function (e) {
console.error(e.message);
});
或者这样:
js
import ResizeObserver from 'resize-observer-polyfill';
const OriginResizeObserver = ResizeObserver;
const ResizeObserver = class ResizeObserver extends OriginResizeObserver {
constructor(callback) {
super((entries, observer) => {
requestAnimationFrame(() => {
callback(entries, observer);
});
});
}
};
不过我的测试结果是不行,这里我没搞懂原因,有知道的大神可以指点一下~
总结
这个错误可以通过赋值默认dataSource
为[{}]
来解决,实际上,如果你的系统有监控系统,其实是可以忽略过滤掉这个错误的,它并不会对影响实际的渲染。
重新梳理一遍流程:
js
renderWithHooks -> Component -> Table -> Body -> data.length -> ExpandedRow -> style width
renderWithHooks -> Component -> Table -> ResizeObserver -> SingleObserver -> ResizeObserverPolyfill
可以看到,通过一步一步调试技巧,可以很好排查第三方库源码问题,而对于自身业务出现的错误,那就更加简单了。