Виртуализация больших списков

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

Компонент n-virtual-list основан на принципе “windowing”: он создает контейнер фиксированной высоты и отображает только те элементы, которые находятся в пределах видимой области. Все остальные элементы остаются в виртуальном пространстве, не создавая лишних узлов в DOM.


Основная структура компонента

Простейший пример использования n-virtual-list выглядит так:

<template>
  <n-virtual-list
    :items="items"
    :item-height="50"
    height="400"
  >
    <template #default="{ item }">
      <div class="list-item">{{ item.text }}</div>
    </template>
  </n-virtual-list>
</template>

<script setup>
import { ref } from 'vue'

const items = ref(
  Array.from({ length: 10000 }, (_, i) => ({ text: `Элемент ${i + 1}` }))
)
</script>

<style>
.list-item {
  display: flex;
  align-items: center;
  height: 50px;
  padding: 0 10px;
  border-bottom: 1px solid #eee;
}
</style>

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

  • items — массив данных для отображения. Каждый элемент должен быть уникальным, особенно при динамическом обновлении.
  • item-height — фиксированная высота каждого элемента списка. Если элементы имеют разную высоту, необходимо использовать режим динамической виртуализации.
  • height — высота контейнера списка, определяющая область видимости.

Поддержка динамической высоты элементов

Если элементы списка имеют разную высоту, Naive UI позволяет использовать dynamic-height. Для этого нужно передавать функцию, которая возвращает высоту каждого элемента:

<n-virtual-list
  :items="items"
  :item-height="getItemHeight"
  height="400"
>
  <template #default="{ item }">
    <div>{{ item.text }}</div>
  </template>
</n-virtual-list>

<script setup>
const getItemHeight = (item) => item.height || 50

const items = ref(
  Array.from({ length: 5000 }, (_, i) => ({
    text: `Элемент ${i + 1}`,
    height: Math.floor(Math.random() * 50) + 50
  }))
)
</script>

Использование функции для высоты позволяет компоненту корректно рассчитывать виртуальную область, предотвращая визуальные “прыжки” при скролле.


Инфинити-скролл и подгрузка данных

Компонент поддерживает реактивное обновление массива и интеграцию с подгрузкой данных по мере прокрутки. Основной подход:

  1. Использовать @scroll событие для отслеживания текущей позиции.
  2. Проверять, приближается ли пользователь к концу списка.
  3. Добавлять новые элементы в массив items.

Пример:

<n-virtual-list
  :items="items"
  :item-height="50"
  height="400"
  @scroll="handleScroll"
>
  <template #default="{ item }">
    <div>{{ item.text }}</div>
  </template>
</n-virtual-list>

<script setup>
const items = ref(
  Array.from({ length: 100, }, (_, i) => ({ text: `Элемент ${i + 1}` }))
)

const handleScroll = (e) => {
  const { scrollTop, clientHeight, scrollHeight } = e.target
  if (scrollTop + clientHeight >= scrollHeight - 100) {
    const nextItems = Array.from({ length: 50 }, (_, i) => ({
      text: `Элемент ${items.value.length + i + 1}`
    }))
    items.value.push(...nextItems)
  }
}
</script>

Особенности:

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

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

Для больших массивов важно использовать ключи элементов через :key, чтобы Vue корректно отслеживал изменения:

<template #default="{ item, index }">
  <div :key="index">{{ item.text }}</div>
</template>

Дополнительно можно комбинировать виртуализацию с debounce скролл-событий, чтобы снизить нагрузку на обработчики событий при быстром скролле.


Стилевое оформление и кастомизация

Компонент позволяет полностью контролировать стили:

  • Высота и отступы элементов (item-height, CSS padding и margin).
  • Скроллбар контейнера через CSS overflow-y.
  • Виртуальная обертка n-virtual-list может принимать кастомные классы для интеграции с темами.

Пример кастомного оформления:

.n-virtual-list {
  border: 1px solid #ccc;
  border-radius: 6px;
  background-color: #fafafa;
}
.list-item {
  border-bottom: 1px solid #eee;
  transition: background 0.2s;
}
.list-item:hover {
  background-color: #f0f0f0;
}

Сравнение с обычным списком

При рендеринге 10 000 элементов обычный <div> список вызывает сильные задержки и возможные падения браузера. Виртуализация через n-virtual-list:

  • Сохраняет высокую скорость скролла.
  • Снижает количество DOM-узлов до видимых элементов.
  • Обеспечивает плавный UX даже при динамических изменениях списка.

Дополнительные возможности

  • Поддержка slot header и footer для вставки фиксированных элементов списка.
  • Интеграция с компонентами n-checkbox, n-button, n-avatar внутри элементов без потери производительности.
  • Полная реактивность массива, включая добавление, удаление и сортировку элементов.

В сочетании с Vue 3 и Composition API, Naive UI позволяет создавать масштабируемые интерфейсы со списками от нескольких сотен до десятков тысяч элементов без снижения отзывчивости.