antd表格性能差的解决方案

13,238 阅读2分钟

场景

项目部署到现场后,现场客户端机子相对性能比较差,在使用antd表格时候,发现数据多时性能特别差,滚动页面会特别卡顿。

排查过程

1.监控性能,找出影响性能的罪魁祸首

先使用控制台的Performance监控性能,看看页面滚动的时候,究竟是什么东西影响了性能, 如图:

监测性能.png

可以看出,页面监控滚动了6+秒,有4+秒都在执行脚本解析,有大量的mouse eventfunction 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,如图:

image.png

到这里,性能差的根本原因已经找到,也发现这些多余的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;

配置后,再次监控性能。

image.png

问题解决!