Анализ дислокации и проблем с производительностью компонентов Antd Table

React.js

Ноль. Простые решения ошибок

  1. При написании фиксированной ширины и высоты пишите фиксированную ширину и высоту. (Исправление простое, но таким образом таблица негибкая и не может использоваться для динамического изменения высоты)
  2. Для простых сценариев вы можете использовать setTimeout для запуска syncFixedTableRowHeight ниже после монтирования. (Недостаток в том, что он нестабилен, и может понадобиться написать функцию мониторинга для проверки загрузки ресурсов, но это снижает вероятность багов)
  3. Свойства слушателя с использованием ResizeroBServer Изменяют высоту или ширину, варьируются в информации синхронизации, метод ниже. (Безупречно)

1. Предисловие и воспроизведение ошибки

В процессе создания механизма активности я обнаружил, что компонент таблицы Antd будет отправлять различные дислокации строк и дислокаций столбцов. Несоответствие строк не рассматривается в этом разделе. В этой статье в основном представлена ​​ошибка несовпадения столбцов, возникающая при включении фиксированных столбцов (то есть при использовании фиксированных), и ряд связанных с ними проблем с производительностью.

На следующем рисунке показано явление при использовании фиксированного столбца и изображения в качестве содержимого столбца, этот случай очень легко воспроизвести.

код

import { Table } from 'antd';

const columns = [
  {
    title: 'Full Name',
    width: 100,
    dataIndex: 'name',
    key: 'name',
    fixed: 'left',
  },
  {
    title: 'Age',
    width: 100,
    dataIndex: 'age',
    key: 'age',
    fixed: 'left',
  },
  { title: 'Column 1', dataIndex: 'address', key: '1' },
  { title: 'Column 2', dataIndex: 'address', key: '2' },
  { title: 'Column 3', dataIndex: 'address', key: '3' },
  { title: 'Column 4', dataIndex: 'address', key: '4' },
  { title: 'Column 5', dataIndex: 'address', key: '5' },
   {
    title: 'Avatar',
    width: 200,
    dataIndex: 'img',
    key: 'img',
    render: (a, row ,b) => (
      <img src={row.img}></img>
    )
  },
  { title: 'Column 6', dataIndex: 'address', key: '6' },
  { title: 'Column 7', dataIndex: 'address', key: '7' },
  { title: 'Column 8', dataIndex: 'address', key: '8' },
  {
    title: 'Action',
    key: 'operation',
    fixed: 'right',
    width: 100,
    render: () => <a href="javascript:;">action</a>,
  },
];

const data = [
  {
    key: '1',
    name: 'John Brown',
    age: 32,
    address: 'New York Park',
    img: 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1566139960708&di=b71fcbe1841e966f5fd3983197628ff9&imgtype=0&src=http%3A%2F%2Fpic1.16xx8.com%2Fallimg%2F161122%2F1F0035M6-7.jpg'
  },
  {
    key: '2',
    name: 'Jim Green',
    age: 40,
    address: 'London Park',
    img: 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1566139968891&di=c0b7ecc441817226dabc251d870f6d22&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01a949581aeb9fa84a0d304fd05eeb.jpg'
  },
];

ReactDOM.render(<Table columns={columns} dataSource={data} scroll={{ x: 1300 }} />, mountNode);

Однако ту же ситуацию было почти невозможно воспроизвести при использовании Element раньше, поэтому я использовал Element-React, и, конечно же, Element-React не будет иметь смещения столбцов или строк, независимо от того, как вы играете.

2. Рендеринг фиксированных столбцов в HTML

Вообще говоря, все без исключения основные методы рендеринга с фиксированным столбцом отображают столбец с фиксированным столбцом как отдельный компонент таблицы. Затем используйте абсолютное позиционирование (position: absolute), чтобы прикрепить его к левой и правой сторонам таблицы.

Вопрос. Каким образом атрибут Фиксированный столбец (Фиксированный) синхронизирует высоту основной ячейки таблицы содержимого и левой и правой фиксированных ячеек таблицы?

Абсолютный макет приведет к тому, что информация о макете (например, высота ячейки, ширина ячейки) элементов столбца между фиксированной таблицей и основной таблицей будет разделена. Фрагментированная информация должна быть каким-то образом синхронизирована. Здесь Element — React и Antd имеют большой разрыв в реализации исправленного для Table.

1. Схема расположения синхронизации элементов:

Одна и та же таблица будет разделена на три части, а узлы Dom внутри будут иметь одинаковый стиль, разница в том, что часть фиксированной таблицы не будет видимой.

Можно сказать, что Element — React’s Table жертвует производительностью, чтобы компенсировать проблему несовпадения стилей. Такие проблемы с производительностью будут проявляться при увеличении количества столбцов и сложности элементов столбцов.

Сначала взгляните на структуру элемента после рендеринга — свойство Fixed в React:

Ниже приведено визуальное представление этой конструкции:

Видно, что структура основной таблицы, а также левой и правой фиксированных таблиц абсолютно одинакова, что можно назвать пустой тратой времени. . . Первоначально нужно было только отобразить соответствующий столбец таблицы, но нужно сказать, что подход Element действительно решает проблему синхронизации информации о макете.

2.Схема компоновки синхронизации Antd:

Ядро Antd Table использует компонент Rc-table. Rc-table не будет отображать те же самые 3 таблицы, а только отображать необходимые столбцы.Видно, что этот дизайн разумен. . .

Тогда, несмотря на разумный дизайн, Antd произвел много ошибок дислокации. Это можно объяснить некоторыми несоответствиями между Rc-table и остальными независимыми компонентами Antd. Это по-прежнему нацелено только на ошибки смещения столбцов.

Перейдите непосредственно к исходному коду Rc-таблицы, чтобы найти сегмент кода для синхронизации фиксированной высоты столбца таблицы:

syncFixedTableRowHeight = () => {
    //...
    //搜寻主 Table 所有行元素
    const bodyRows = this.bodyTable.querySelectorAll(`.${prefixCls}-row`) || [];
    //...
    const state = this.store.getState();
    //获取主 Table 的行高
    const fixedColumnsBodyRowsHeight = [].reduce.call(
      bodyRows,
      (acc, row) => {
        const rowKey = row.getAttribute('data-row-key');
        const height =
          row.getBoundingClientRect().height || state.fixedColumnsBodyRowsHeight[rowKey] || 'auto';
        acc[rowKey] = height;
        return acc;
      },
      {},
    );
    
    //比较是否发生了,如果没有发生变化,就返回
    if (
      shallowequal(state.fixedColumnsHeadRowsHeight, fixedColumnsHeadRowsHeight) &&
      shallowequal(state.fixedColumnsBodyRowsHeight, fixedColumnsBodyRowsHeight)
    ) {
      return;
    }

    //如果发生了变化,就同步变化
    this.store.setState({
      fixedColumnsHeadRowsHeight,
      fixedColumnsBodyRowsHeight,
    });
};

Он будет вызываться при монтировании и событии изменения размера документа.

Но тут есть баг, есть еще много элементов, которые не отрисовываются при монтировании, например Image. При синхронизации syncFixedTableRowHeight высота изображения не будет рассчитываться, что приведет к фрагментации высоты.Хотя функция срабатывает, проблемы с высотой синхронизации нет.

3. Как исправить ошибку синхронизации столбцов rc-table

Поэтому, после понимания принципа, естественно подумать о явном указании высоты css для изображения.. Таким образом, при установке вы можете Вызовите syncFixedTableRowHeight, чтобы получить высоту, даже если изображение не было обработано.

Идеальное решение: использовать ResizeObserver, почти все проблемы с дислокацией Antd решаются этим методом, (может из-за совместимости браузеров этот PR лежит уже больше полугода, но думаю обслуживание несвоевременно.. .), даже если это проблема совместимости, ResizeObserver имеет полифилл, основанный на MutationObserver, а основные браузеры поддерживают MutationObserver.

Ниже приведен код решения, если будете строить колесо в будущем, то можете на него ссылаться:

createObserver() {
    return new ResizeObserver(entries => {
      const state = this.store.getState();
	
      const fixedColumnsHeadRowsHeight = { ...state.fixedColumnsHeadRowsHeight };
	
      const fixedColumnsBodyRowsHeight = { ...state.fixedColumnsBodyRowsHeight };
	
      const firstRowCellsWidth = { ...state.firstRowCellsWidth };
	
      for (let i = 0; i < entries.length; i++) {
        const entry = entries[i];
        const { target } = entry;
        const headerRowIndex = target.getAttribute('data-header-row-index')
        const rowKey = target.getAttribute('data-row-key');
        const columnKey = target.getAttribute('data-column-Key')
        const { width, height } = target.getBoundingClientRect();
        
        if (headerRowIndex !== null) {
          if (fixedColumnsHeadRowsHeight[headerRowIndex] !== height) {
            fixedColumnsHeadRowsHeight[headerRowIndex] = height;
          }
        }
        if (rowKey !== null) {
          if (fixedColumnsBodyRowsHeight[rowKey] !== height) {
            fixedColumnsBodyRowsHeight[rowKey] = height;
          }
        }
        if (columnKey !== null) {
          if (
            firstRowCellsWidth[columnKey] === undefined ||
            width !== firstRowCellsWidth[columnKey]
          ) {
            firstRowCellsWidth[columnKey] = width;
          }
        }
      }
      this.store.setState({
        fixedColumnsHeadRowsHeight,
        fixedColumnsBodyRowsHeight,
        firstRowCellsWidth,
      });
    });
  }

Теперь, когда проблема высоты макета решена, как решить динамический атрибут синхронизации?

Это динамическое свойство похоже на scrollTop, scrollLeft, hoverCellIndex и т. д. Здесь реализация Element и Antd абсолютно одинакова.

Например, как синхронизировать свойства onScroll трех таблиц, мы можем прослушивать событие onScroll основной таблицы. Затем распределите scrollTop и scrollLeft основной таблицы на левую и правую фиксированные таблицы. Результатом этого является запуск 3 перекрасок.

syncScroll() {
    const { headerWrapper, footerWrapper, bodyWrapper, fixedBodyWrapper, rightFixedBodyWrapper } = this;
    if (headerWrapper) {
      headerWrapper.scrollLeft = bodyWrapper.scrollLeft;
    }
    if (footerWrapper) {
      footerWrapper.scrollLeft = bodyWrapper.scrollLeft;
    }

    if (fixedBodyWrapper) {
      fixedBodyWrapper.scrollTop = bodyWrapper.scrollTop;
    }
    if (rightFixedBodyWrapper) {
      rightFixedBodyWrapper.scrollTop = bodyWrapper.scrollTop;
    }
  }

// 主 Table

<div
  style={this.bodyWrapperHeight}
  className="el-table__body-wrapper"
  ref={this.bindRef('bodyWrapper')}
  onScroll={this.syncScroll}
>
  <TableBody
    {...this.props}
    style={{ width: this.bodyWidth }}
  />
</div>

Сравнение производительности

Раздел элемента

Здесь мы переходим непосредственно к ссылке сравнения производительности, чтобы доказать, насколько вредна производительность дизайна Element для фиксированного столбца.

Ниже приведен скриншот производительности от инициализации до постоянной прокрутки, запускающей перерисовку onScroll. (0–10 мс)

при прокрутке

Сначала посмотрите на стек вызовов и общее соотношение времени обработки:

Этот снимок экрана может доказать, что дизайн таблицы элементов для свойства Fixed представляет собой большую проблему, и большая часть времени тратится на рендеринг, который во многом связан с его дизайном. Если мы уберем свойство Fixed и проверим его снова:

Можно обнаружить, что разрыв в производительности при рендеринге очень велик:

и раздел

Ниже вы можете видеть, что производительность Antd на таблице ничуть не лучше. Время рендеринга очень короткое, и если вы внимательно посмотрите на код, Antd и Rc-table поработали над некоторыми деталями, такими как debounce и Throttle. Я не буду перечислять их здесь по одному, я только читал код и не проводил экспериментов, чтобы проверить, насколько он оптимизирован.

Однако, поскольку он расширен, я скажу еще несколько слов: Antd действительно соответствует общим фоновым требованиям по Table, и большинство проблем с производительностью можно решить с помощью пейджинга. Однако для многих сценариев больших данных производительность Antd на самом деле очень общая, и для сценариев больших данных нет оптимизации. Там много методов оптимизации. Одним из самых практичных является виртуальная прокрутка.

Сравнение дизайна таблицы

Из-за различных решений Fixed это также частично вызвано разницей в дизайне таблицы.

React-Element

Хранилище таблиц будет одновременно служить источником данных для трех таблиц, и даже эти три таблицы почти одинаковы, за исключением отображаемой части. Однако видно, что атрибутов, специфичных для фиксированных данных, не так много, а код в целом очень лаконичен, можно сказать, что пространство (имеется в виду количество кодов) используется для времени.

Rc-Table

Связь Rc-Table && Antd Table

Окончательные предложения по улучшению Antd и Element

Строго говоря, это предложение по улучшению Rc-table в надежде на продвижение обновления ResizeObserver.

Для Element я надеюсь убрать дизайн Fixed, что является серьезной потерей производительности. Теоретически это вызовет 3-кратную потерю производительности, но в реальности в более сложной среде эта потеря производительности будет еще больше.

В дизайне Table Antd лучше Element, но он уступает Rc-table.Rc-table на данный момент отстает в сопровождении.Честно говоря, я надеюсь, что Antd сможет реализовать набор компонентов Table-core самостоятельно. Независимо от того, какой из них, в настоящее время, похоже, есть много возможностей для оптимизации.