DevGang
Авторизоваться

Виртуальная прокрутка в React: реализация с нуля и использование react-window

Сколько раз вы сталкивались с ситуацией, когда в веб-приложении необходимо вывести список элементов? Это очень частое явление при создании современных веб-приложений. Проблема возникает, когда нужно вывести очень большой набор данных, скажем, 100 000 или более единиц одновременно, да еще и без пагинации. Это приведет к загрязнению DOM и займет много памяти браузера, что приведет к проблемам с производительностью и снижению качества обслуживания пользователя.

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

В этой статье мы рассмотрим, что такое виртуальная прокрутка и как можно реализовать виртуальную прокрутку в приложении React для вывода списка из 100 000 элементов без пагинации.

Что такое виртуальная прокрутка?

Как мы уже говорили, отображение огромного списка данных в DOM без пагинации может замедлить работу приложения из-за загрязнения DOM, потребления слишком большого объема памяти браузера, и, кроме того, конечный пользователь не будет использовать все данные сразу, он увидит только часть этих данных, а затем прокрутит список, чтобы увидеть больше данных.

Виртуальная прокрутка или windowing - это техника разработки веб-приложений, при которой отображаются только те элементы, которые видны в области просмотра, а остальные элементы виртуализируются с помощью верхней и нижней подкладки. По мере прокрутки списка старые элементы будут уничтожаться, а новые отображаться в зависимости от положения полосы прокрутки. Таким образом, мы не будем слишком нагружать DOM, отображая всё сразу. Использование этой техники в правильном сценарии может значительно повысить производительность приложения.

Нужна ли нам виртуализация?

Теперь, когда мы знаем, что такое виртуальная прокрутка, возникает вопрос: "А всегда ли нам нужна виртуальная прокрутка?".

Ответ на этот вопрос должен быть вполне очевиден: если вы создаете веб-приложение, в котором необходимо отображать огромный список элементов без пагинации, и вам действительно важна производительность, то, безусловно, виртуальная прокрутка - это то, что вы можете использовать.

Несмотря на то, что это хороший способ повысить производительность, есть несколько сценариев, в которых виртуальная прокрутка может и не понадобиться:

  • Если список состоит менее чем из 50 или 100 данных. Если список состоит из небольшого количества элементов, например 50 или 100, виртуальная прокрутка может быть просто накладной и не окажет существенного влияния на производительность приложения.
  • При отображении пагинационного списка. При отображении списка с некоторой пагинацией, очевидно, виртуальная прокрутка может оказаться бесполезной, поскольку весь смысл виртуальной прокрутки заключается в эффективном отображении элементов списка, когда пагинация не реализована.

Виртуализация в приложении React с нуля

Теперь, когда мы имеем некоторое представление о виртуальной прокрутке и о том, когда ее следует использовать, посмотрим, как можно реализовать виртуальную прокрутку в react-приложении без использования каких-либо сторонних инструментов.

Чтобы приступить к реализации этого подхода, сделаем несколько предположений:

  1. Высота каждой строки списка будет фиксированной.
  2. Общее количество элементов, выводимых в списке, известно заранее.

Если условия выполнены, создадим новое приложение React и построим компонент VirtualScroll.

import { useState } from "react";

const VirtualScroll = ({
  rowHeight,
  totalItems,
  items,
  visibleItemsLength,
  containerHeight,
}) => {
  // Calculate the total height of the container
  const totalHeight = rowHeight * totalItems;
  //   Current scroll position of the container
  const [scrollTop, setScrollTop] = useState(0);
  // Get the first element to be displayed
  const startNodeElem = Math.ceil(scrollTop / rowHeight);
  // Get the items to be displayed
  const visibleItems = items?.slice(
    startNodeElem,
    startNodeElem + visibleItemsLength
  );
  //  Add padding to the empty space
  const offsetY = startNodeElem * rowHeight;

  const handleScroll = (e) => {
    // set scrollTop to the current scroll position of the container.
    setScrollTop(e?.currentTarget?.scrollTop);
  };

  return (
    <div
      style={{
        height: containerHeight,
        overflow: "auto",
        border: "5px solid black",
      }}
      onScroll={handleScroll}
    >
      <div style={{ height: totalHeight }}>
        <div style={{ transform: `translateY(${offsetY}px)` }}>
          {visibleItems}
        </div>
      </div>
    </div>
  );
};

export default VirtualScroll;

В приведенном выше коде мы выполняем очень простые математические действия: сначала вычисляем общую высоту контейнера totalHeight путем умножения высоты одной строки rowHeight на общее количество элементов, подлежащих отображению totalItems.

Затем мы делим позицию прокрутки контейнера сверху scrollTop на высоту одной строки rowHeight, чтобы получить начальный элемент узла startNodeElem, который будет отображаться в списке, мы также используем Math.ceil для округления значения.

Затем мы нарезаем totalItems, чтобы показать только элементы, начиная с startNodeElem и заканчивая startNodeElem + visibleItemsLength. Так, допустим, наш startNodeElem = 5, тогда мы будем показывать элементы с 5-й позиции до 5 + visibleItemsLength, считая, что visibleItemsLength в данном случае равна 20. Таким образом, наш компонент будет отображать элементы, начиная с 5-й позиции и заканчивая 5 + 20 = 25 позициями.

Теперь каждый раз, когда пользователь прокручивает содержимое, значение scrollTop будет пересчитываться, а startNodeElem будет обновляться в зависимости от положения прокрутки, и мы будем смещать узлы вниз с помощью свойства transform: translateY.

Теперь мы можем использовать наш компонент VirtualScroll для отображения списка элементов.

import React from "react";
import VirtualScroll from "./virtual-scroll";
// Total items to be rendered
const totalItems = 100000;

const items = new Array(totalItems).fill(null).map((_, index) => {
  return (
    // Height of the div should be the same as what we are sending in rowHeight
    <div style={{ height: "70px" }} key={index}>
      <p>Row Number - {index}</p>
    </div>
  );
});

function App() {
  return (
    <>
      <h1>Virtual Scroll From Scratch</h1>
      <VirtualScroll
        rowHeight={70}
        totalItems={totalItems}
        containerHeight="500px"
        items={items}
        visibleItemsLength={20}
      />
    </>
  );
}
export default App;

Здесь мы выводим список из 100000 элементов и устанавливаем значение containerHeight равным 500px, а высоту каждой строки или rowHeight равным 70px.

Теперь запустите свое приложение, чтобы увидеть виртуальную прокрутку в действии.

Как видите, при прокрутке списка мы не накапливаем лишние узлы, а уничтожаем предыдущие узлы и отрисовываем новые, основываясь на положении контейнера при прокрутке.

Вот вывод песочницы с приведённой выше реализацией: https://pvg2cx.csb.app/.

Вы можете заметить некоторое белое пространство при прокрутке, это связано с тем, что при каждом прокручивании контейнера происходит манипуляция со всем DOM, и мы заново все вычисляем.

Таким образом, это была очень наивная реализация виртуального скроллинга с нуля в react-приложении, которая, конечно, может быть оптимизирована для достижения лучших результатов.

Виртуальная прокрутка в React с использованием react-window

Как мы видели в предыдущем разделе, мы можем реализовать виртуальную прокрутку с нуля. В этом разделе мы рассмотрим сторонний инструмент react-window, который позволяет реализовать виртуальный скроллинг гораздо проще.

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

Чтобы начать работу с react-window, нам сначала нужно установить его в наше react-приложение.

# Yarn
yarn add react-window

# NPM
npm install --save react-window

С помощью react-window мы можем выводить списки с фиксированной высотой строки, а также списки с динамической высотой строки, пройдемся по каждому из них по очереди.

React-window со списком фиксированного размера

Если необходимо вывести элементы списка с фиксированной высотой строки, можно воспользоваться компонентом FixedSizeList из react-window:

import { FixedSizeList } from "react-window";

const Row = ({ index, style }) => (
  /* Adding style attribute is very important here
    it supplies the row height to the elements. */
  <div style={style}>Row {index}</div>
);

// Create a component with Fixed Size example
const ReactWindowFixedSizeEx = () => (
  <FixedSizeList height={150} itemCount={100000} itemSize={35} width={300}>
    {Row}
  </FixedSizeList>
);

export { ReactWindowFixedSizeEx };

В приведенном выше примере мы установили высоту контейнера равной 150px, а высота каждой строки будет равна 35px.

Демонстрация: https://m5tkt9.csb.app/.

React-window со списком изменяемого размера

Если необходимо вывести элементы списка с динамической высотой строки, можно воспользоваться компонентом VariableSizeList из react-window:

import { VariableSizeList } from "react-window";
import "./App.css";
const totalItemsCount = 100000;

// Set random row heights
const rowHeights = new Array(totalItemsCount)
  .fill(true)
  .map(() => 12 + Math.round(Math.random() * 50));

const getItemSize = (index) => rowHeights[index];

const Row = ({ index, style }) => (
  <div style={style} className={index % 2 ? "odd" : "even"}>
    Row {index}
  </div>
);

// Create a component with Fixed Size example
const ReactWindowVariableSizeEx = () => (
  <VariableSizeList
    height={500}
    itemCount={totalItemsCount}
    itemSize={getItemSize} 
    width={300}
  >
    {Row}
  </VariableSizeList>
);

export default ReactWindowVariableSizeEx;

В приведенном примере высота контейнера будет равна 500px, а высота ряда будет произвольной, и мы используем свойство itemSize для указания высоты конкретного ряда.

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

Демонстрация: https://64ynfm.csb.app/.

Отлично! Итак, мы рассмотрели различные способы реализации виртуальной прокрутки в react-приложении, вот ссылка на репозиторий GitHub с каждой из этих реализаций, если вы хотите проверить - https://github.com/imvedanshmehra/virtual-scroll-react.

Заключительные слова

Виртуализация - очень хорошая техника для повышения производительности приложения при рендеринге большого набора данных без пагинации, ее можно реализовать с нуля или использовать пакеты типа react-window.

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

Я надеюсь, что вы получили такое же удовольствие от чтения этой статьи, как и я от ее написания, и надеюсь, что вы сможете почерпнуть из нее что-то новое!

Источник:

#JavaScript #React
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться