Виртуализация списков

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

Создание базового виртуализированного списка

Для начала необходимо определить собственный элемент с SkateJS:

import { component, h, prop } from 'skatejs';

class VirtualList extends HTMLElement {
  static props = {
    items: prop.array(),
    itemHeight: prop.number({ default: 30 }),
    visibleCount: prop.number({ default: 10 }),
    scrollTop: prop.number({ default: 0 })
  };

  render() {
    const start = Math.floor(this.scrollTop / this.itemHeight);
    const end = start + this.visibleCount;
    const visibleItems = this.items.slice(start, end);

    const paddingTop = start * this.itemHeight;
    const paddingBottom = (this.items.length - end) * this.itemHeight;

    return h('div', { style: { overflowY: 'auto', height: `${this.itemHeight * this.visibleCount}px` },
      onScroll: (e) => this.scrollTop = e.target.scrollTop
    }, [
      h('div', { style: { paddingTop: `${paddingTop}px`, paddingBottom: `${paddingBottom}px` } },
        visibleItems.map(item => h('div', { style: { height: `${this.itemHeight}px` } }, item))
      )
    ]);
  }
}

component(VirtualList);

Ключевые моменты:

  • Свойство scrollTop отслеживает текущую позицию прокрутки.
  • Расчет видимых элементов выполняется через индексы start и end.
  • Используется padding для создания эффекта полной длины списка без рендеринга всех элементов.

Реактивность и обновление списка

SkateJS позволяет автоматически обновлять отображение при изменении массива items или позиции прокрутки. Свойства с типом prop.array() и prop.number() поддерживают реактивное обновление. Это означает, что при изменении this.items компонент перерендерится без необходимости вручную вызывать методы обновления DOM.

this.items.push('Новый элемент');

После выполнения этой операции список пересчитается и отобразит новые элементы в видимой области.

Оптимизация рендеринга

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

Дополнительно можно использовать requestAnimationFrame для плавного обновления при прокрутке:

onScroll: (e) => {
  requestAnimationFrame(() => this.scrollTop = e.target.scrollTop);
}

Это предотвращает чрезмерные вызовы рендера при быстром скролле.

Стилизация и кастомизация

Каждый элемент списка можно стилизовать независимо через inline-стили или CSS-классы. Также можно создавать более сложные шаблоны элементов, включая вложенные компоненты:

visibleItems.map(item => h('list-item', { item }));

При этом list-item может быть отдельным SkateJS-компонентом с собственной логикой и жизненным циклом.

Динамическая высота элементов

В некоторых случаях элементы списка могут иметь разную высоту. Для этого требуется хранить массив высот и суммарные отступы:

const start = this.calculateStartIndex(this.scrollTop);
const end = this.calculateEndIndex(this.scrollTop);

Методы calculateStartIndex и calculateEndIndex определяют, какие элементы должны быть видимы, учитывая высоту каждого элемента. Это более сложная, но гибкая схема, позволяющая виртуализировать динамические списки.

Интеграция с внешними данными

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

async fetchMoreItems() {
  const newItems = await fetch('/api/items').then(res => res.json());
  this.items = [...this.items, ...newItems];
}

Компонент автоматически отрендерит новые видимые элементы без потери производительности.

Жизненный цикл компонента

SkateJS предоставляет методы жизненного цикла, которые полезны при виртуализации:

  • connectedCallback() — инициализация обработчиков скролла.
  • disconnectedCallback() — удаление подписок для предотвращения утечек памяти.
  • updated() — обработка изменений items или других свойств, например пересчет отступов.

Дополнительные улучшения

  • Debounce прокрутки для уменьшения частоты рендера.
  • Placeholder элементы для улучшения UX при асинхронной загрузке данных.
  • Lazy-loading изображений внутри элементов списка для снижения потребления ресурсов.

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