Несмотря на то, что React довольно эффективен «из коробки», иногда вам нужно его настроить. Самый распространенный трюк — реализовать метод жизненного цикла shouldComponentUpdate, чтобы React мог пропустить рендеринг на основе пользовательской проверки. Это может быть удобно, если проверка на равенство с данными оказывается дешевой (например, вы используете какую-то библиотеку, предоставляющую неизменяемые структуры).
Иногда этого недостаточно. Рассмотрите возможность рендеринга тысяч строк табличных данных. Это может быстро превратиться в тяжелую операцию, даже если у вас есть хорошие проверки. Именно тогда вам нужно быть более умным и реализовать контекстно-зависимую оптимизацию.
Если табличные данные хорошо вписываются в область просмотра (подумайте о переполнении CSS), можно отобразить только видимые строки и пропустить отрисовку остальных. Этот метод — виртуализация — стоит подробно изучить, поскольку он дает вам более полное представление о React.

Основная идея виртуализации

Самым большим ограничением виртуализации является то, что вам нужно какое-то окно просмотра. Вы могли бы рассматривать даже окно браузера как таковое. Однако часто вы устанавливаете что-то более ограниченное. Простой способ добиться этого — зафиксировать ширину/высоту контейнера и установить свойство overflow: auto через CSS. Затем данные будут отображаться внутри контейнера.
Вопрос в том, как понять, что отображать? Наивный случай прост. Предполагая, что мы рендерим строки таблицы, мы сначала будем рендерить только столько строк, сколько помещается в окне просмотра. Допустим, окно просмотра имеет высоту 400 пикселей, а каждая строка занимает по 40 пикселей. Исходя из этого, мы можем сказать, что можем сначала отрендерить до десяти строк.

Проблема прокрутки

Проблемы начинаются, когда пользователь начинает прокручивать страницу. Мы можем захватить информацию, связанную с прокруткой, с помощью обработчика aonScroll. Там мы можем найти текущую координату y верхней позиции прокрутки через e.target.scrollTop.
Допустим, пользователь прокрутил страницу на 50 пикселей по вертикали. Быстрая математика подсказывает нам, что мы должны пропустить рендеринг двух строк (50–20 * 2 = 10) и начать рендеринг с третьего. Мы также должны сместить содержимое на 30 пикселей (50–20 = 30). Один из способов справиться со смещением – отобразить дополнительную строку в начале и установить ее высоту соответствующим образом.
Аналогичная проблема возникает и со строками в конце. Поскольку мы также хотим пропустить строки рендеринга, нам нужно будет выполнить аналогичные математические действия. На этот раз мы выясним это на основе высоты области просмотра с учетом смещения. Чтобы получить правильную высоту полосы прокрутки, мы можем отобразить дополнительную строку, используя эту информацию.
На основе этих вычислений и небольшой дополнительной работы мы можем выяснить следующее:
* Дополнительные отступы в начале — дополнительная строка, чтобы полоса прокрутки выглядела правильно.
* Количество строк для рендеринга, включая местоположение — они должны быть вырезаны из всего набора данных.
* Дополнительный отступ в конце — дополнительная строка, чтобы полоса прокрутки выглядела правильно.

Проблема индексов

Это еще не все. В схеме есть пара косяков. Если мы применили к нашему контенту четные/нечетные стили с помощью CSS, вы довольно быстро увидите, что что-то не так. Базовый алгоритм приводит к мерцанию при прокрутке.
Мерцание связано с тем, что мы всегда выполняем рендеринг с «нуля» без учета фактического индексирования. К счастью, эту проблему легко исправить. Как только вы узнаете, откуда вы нарезаете данные, вы можете проверить, является ли начальный индекс четным или нет. Если он четный, визуализируйте дополнительную строку в начале. Он может иметь нулевую высоту. Этот небольшой чит делает результат рендеринга стабильным и избавляет от мерцания.

Проблема высоты

В приведенном выше примере мы упростили себе задачу, предположив, что для высоты строки задано определенное значение. Это самый распространенный способ приблизиться к нему и достаточно часто. Что если, однако, вы разрешили произвольную высоту строк?
Здесь все становится интереснее. Внедрить виртуализацию по-прежнему можно, но необходимо выполнить дополнительный шаг — измерение.

ИДЕЯ ИЗМЕРЕНИЯ

Одним из способов решения этой проблемы является обработка ее с помощью функции React, известной как контекст и обратные вызовы, для обновления информации о высоте на более высоком уровне, где существует логика. В зависимости от вашего дизайна, обычные реквизиты также могут работать, или вы можете использовать решение для управления состоянием для этой части. Нет правильного пути.
Идея состоит в том, что после вызова метода жизненного цикла componentDidMount или componentDidUpdate для строки вы инициируете обратный вызов с информацией о высоте строки. Вы можете зафиксировать это, используя поле ref andoffsetHeight.
Вы также можете передать информацию об идентификаторе, связанную со строкой, в функцию обратного вызова. Это позволяет различать измерения. Лучше использовать фактический идентификатор строки вместо индекса, так как последний не даст предсказуемых результатов.

ПРОВЕДЕНИЕ ПЕРВОНАЧАЛЬНЫХ ИЗМЕРЕНИЙ

Помимо измерения, вам нужно будет использовать данные измерений для выяснения того же, что мы сделали выше. Математика немного сложнее, но идеи те же. Самой большой трудностью является обработка первоначального рендера. У вас там нет данных измерений, и вам нужно их как-то получить.
В итоге я решил эту проблему, реализовав методы componentDidMount и componentDidUpdatelifecycle на логическом уровне. Важно отметить, что если вы используете встроенное решение CSS, такое как Радий, исходные данные измерения могут быть недействительными! Решениям требуется некоторое время, чтобы применить их стили, и в результате методы жизненного цикла React могут работать не так, как вы ожидаете.
Чтобы дать вам приблизительное представление о том, как я решил эту проблему, я получил следующий код:

...
class VirtualizedBody extends React.Component {
  ...
  componentDidMount() {
    // Trigger measuring initially
    this.checkMeasurements();
  },
  componentDidUpdate() {
    // Called due to forceUpdate -> cycle continues if
    // there are no measurements yet
    this.checkMeasurements();
  },
  ...
  checkMeasurements() {
    if (this.initialMeasurement) {
      const rows = calculateRows(...);
      if (!rows) {
        return;
      }
      // Refresh the rows to trigger measurement
      setTimeout(() => {
        this.forceUpdate(
          () => {
            // Recalculate rows upon completion
            this.setState(
              rows,
              // If calculateRows returned something, (not {}), finish
              () => this.initialMeasurement = false
            );
          }
        );
      }, 100); // Try again in a while
    }
  }
}
export default VirtualizedBody;

Возможно, это не самое красивое решение, но оно работает в рамках заданных ограничений. Что наиболее важно, это позволяет нам получать исходные данные измерений, которые, в свою очередь, можно экстраполировать, чтобы получить среднюю высоту строки. Чем больше данных мы визуализируем, тем лучшее среднее значение мы можем получить.
В такой схеме мы чаще всего будем иметь дело с неполными данными. На практике иметь что-то достаточно хорошее — это ценно. Особенно если вы обрабатываете тысячи строк, небольшие неточности не сильно повлияют на отрисовку полосы прокрутки.
Наиболее точный результат можно получить, измеряя каждую строку, но это, в свою очередь, снижает нашу производительность. Скорее всего, можно было бы реализовать более сложные измерения для сбора данных о фоне, но до сих пор оказалось достаточно грубого приближения, такого как приведенное выше.

Вывод

Хотя на бумаге виртуализация не кажется сложной проблемой, с ней может быть трудно справиться, когда вы доберетесь до браузера. Это особенно верно, если вы ослабите ограничения и разрешите произвольную высоту строк. Таким образом, вы можете превратить относительно простую задачу в гораздо более сложную. С точки зрения пользователя отсутствие необходимости указывать высоту может быть удобным, даже если это сложнее реализовать.
Самое большое преимущество виртуализации заключается в том, что она существенно сокращает объем рендеринга. Часто окна просмотра несколько ограничены. Вместо тысяч строк в худшем случае вы получите десятки строк. Это огромная разница, даже если вы пропустите выполнениеshouldComponentUpdate для каждой строки. Необходимость в этом просто отпадает.
Вы можете увидеть этот подход в действии на моем настольном компоненте Reactabular. См. React-virtualized для другого взгляда на эту тему.

Первоначально опубликовано на сайте blog.jscrambler.com 4 октября 2016 г.