场景
项目部署到现场后,现场客户端机子相对性能比较差,在使用antd表格时候,发现数据多时性能特别差,滚动页面会特别卡顿。
排查过程
1.监控性能,找出影响性能的罪魁祸首
先使用控制台的Performance监控性能,看看页面滚动的时候,究竟是什么东西影响了性能, 如图:
可以看出,页面监控滚动了6+秒,有4+秒都在执行脚本解析,有大量的mouse event和function call,需要在js线程执行,会严重阻塞渲染线程,问题就出现在这里。(如果对js事件循环机制不了解的小伙伴,可以看看这篇JS事件循环机制
2.阅读源码,找出问题根源代码
看antd Table的源码,发现Table的底层是rc-table,大致代码如下:
...
import RcTable, { Summary } from 'rc-table';
...
return <RcTable<RecordType>
...
/>
...
经查看rc-table的单元格源码,如下:
...
// ====================== Hover =======================
const onMouseEnter: React.MouseEventHandler<HTMLTableCellElement> = event => {
if (record) {
onHover(index, index + mergedRowSpan - 1);
}
additionalProps?.onMouseEnter?.(event);
};
const onMouseLeave: React.MouseEventHandler<HTMLTableCellElement> = event => {
if (record) {
onHover(-1, -1);
}
additionalProps?.onMouseLeave?.(event);
};
...
发现表格的每个单元格,都内置了onMouseEnter和onMouseLeave方法,这两个方法又会执行onHover,看看onHover相关代码,如下:。
...
const onHover = React.useCallback((start: number, end: number) => {
setStartRow(start);
setEndRow(end);
}, []);
const hoverContext = React.useMemo(
() => ({ startRow, endRow, onHover }),
[onHover, startRow, endRow],
);
...
return <HoverContext.Provider value={hoverContext}>{bodyNode}</HoverContext.Provider>;
...
发现onHover会更新Body组件的状态从而触发渲染,同时onHover又作为hoverContext上下文的状态之一,再看看hoverContext相关代码,如下:
function Cell<RecordType extends DefaultRecordType>(
{
const componentProps: React.TdHTMLAttributes<HTMLTableCellElement> & {
...
className: classNames(
cellPrefixCls,
className,
{
...
[`${cellPrefixCls}-row-hover`]: !cellProps && hovering,
},
...
),
...
};
return (
<Component {...componentProps}>
{appendNode}
{childNode}
</Component>
);
}
const WrappedCell = React.forwardRef((props: CellProps<any>, ref: React.Ref<any>) => {
const { onHover, startRow, endRow } = React.useContext(HoverContext);
...
const hovering = inHoverRange(index, mergedRowSpan || 1, startRow, endRow);
return (
<MemoCell
...
onHover={onHover}
/>
);
});
...
export default WrappedCell;
可见onHover最终是为了给鼠标悬浮时所在的单元格Cell添加一个类名,从而触发样式的变更,我们可以实验一下,悬浮后单元格果然都增加了一个新className,如图:
到这里,性能差的根本原因已经找到,也发现这些多余的mouse event并没有什么很大的作用,反而严重影响了性能,所以想办法将其去除。
解决方案
使用antd的Table里的components
属性将自定义单元格,阻止onMouseEnter, onMouseLeave
的透传,解决后表格滚动时性能会提升3倍。
// 注意TdCell要提到DataTable作用域外声明
const TdCell = (props: any) => {
// onMouseEnter, onMouseLeave在数据量多的时候,会严重阻塞表格单元格渲染,严重影响性能
const { onMouseEnter, onMouseLeave, ...restProps } = props;
return <td {...restProps} />;
};
const DataTable: React.FC<tableType> = (props) => {
const { columns,dataSource } = props;
return <Table
columns={columns}
dataSource={dataSource}
components={{
body: { cell: TdCell },
}}
/>
);
};
export default DataTable;
配置后,再次监控性能。
问题解决!