Виртуальные списки

В библиотеке Naive UI компонент NVirtualList предназначен для эффективного отображения длинных списков данных. Основная задача виртуализации — рендерить только видимые элементы, что значительно снижает нагрузку на DOM и повышает производительность приложений.

Компонент NVirtualList поддерживает как вертикальную, так и горизонтальную прокрутку, а также динамическую высоту элементов.

import { createApp } from 'vue';
import { NVirtualList } from 'naive-ui';

const app = createApp({
  setup() {
    const items = Array.from({ length: 10000 }, (_, i) => `Элемент ${i + 1}`);
    return { items };
  },
  template: `
    <n-virtual-list :items="items" :item-size="30" style="height: 400px;">
      <template #default="{ item }">
        <div>{{ item }}</div>
      </template>
    </n-virtual-list>
  `
});

app.mount('#app');
  • items — массив данных, которые нужно отобразить.
  • item-size — высота одного элемента в пикселях (для вертикального списка) или ширина (для горизонтального).
  • style — задаёт размеры контейнера для виртуализации. Без ограниченной высоты или ширины виртуализация работать не будет.
  • #default — слот для рендеринга каждого элемента списка.

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

Если высота элементов не фиксирована, можно использовать :estimated-size, чтобы указать примерную высоту. Это позволяет виртуальному списку более точно рассчитывать прокрутку.

<n-virtual-list
  :items="items"
  :estimated-size="50"
  style="height: 500px;"
>
  <template #default="{ item }">
    <div style="padding: 10px; border-bottom: 1px solid #eee;">
      {{ item }}
    </div>
  </template>
</n-virtual-list>

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

  • estimated-size позволяет компоненту оптимизировать рендер, даже если высота элементов различается.
  • Использование фиксированной высоты по возможности предпочтительнее для высокой производительности.

Горизонтальная прокрутка

Для создания горизонтального виртуального списка необходимо указать свойство horizontal и задать ширину элемента:

<n-virtual-list
  :items="items"
  horizontal
  :item-size="120"
  style="width: 600px; overflow-x: auto; white-space: nowrap;"
>
  <template #default="{ item }">
    <div style="display: inline-block; width: 120px; text-align: center;">
      {{ item }}
    </div>
  </template>
</n-virtual-list>

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

  • horizontal — активирует горизонтальную виртуализацию.
  • item-size в данном случае задаёт ширину элемента.
  • Для корректной работы важно использовать стили white-space: nowrap и overflow-x: auto.

Поддержка бесконечной прокрутки

Для реализации бесконечной загрузки данных используется событие onScroll и проверка позиции прокрутки. Например:

<n-virtual-list
  :items="items"
  :item-size="40"
  style="height: 400px;"
  @scroll="handleScroll"
>
  <template #default="{ item }">
    <div>{{ item }}</div>
  </template>
</n-virtual-list>
setup() {
  const items = ref(Array.from({ length: 100 }, (_, i) => `Элемент ${i + 1}`));
  
  const handleScroll = (e) => {
    const { scrollTop, clientHeight, scrollHeight } = e.target;
    if (scrollTop + clientHeight >= scrollHeight - 50) {
      const nextItems = Array.from({ length: 20 }, (_, i) => `Элемент ${items.value.length + i + 1}`);
      items.value.push(...nextItems);
    }
  };
  
  return { items, handleScroll };
}

Пояснения:

  • Проверка scrollTop + clientHeight >= scrollHeight - 50 позволяет загружать данные заранее, до достижения конца списка.
  • Это обеспечивает плавный опыт бесконечной прокрутки без «подёргиваний».

Оптимизация производительности

  • Использование фиксированной высоты элементов: повышает скорость рендеринга.
  • Фрагментация списка (virtual-scroll-buffer): уменьшает количество одновременно рендеримых элементов.
  • Делегирование событий: вместо назначения обработчиков на каждый элемент, использовать один глобальный обработчик на контейнере.
  • Кэширование компонентов внутри слота: уменьшает повторный рендер при прокрутке.

Пример с компонентами внутри виртуального списка

Можно рендерить полноценные компоненты Vue внутри NVirtualList, что позволяет использовать сложные элементы с состоянием:

import { NCard } from 'naive-ui';

<n-virtual-list :items="items" :item-size="100" style="height: 600px;">
  <template #default="{ item }">
    <n-card title="Карточка">
      <p>{{ item.description }}</p>
    </n-card>
  </template>
</n-virtual-list>
  • NCard из Naive UI полностью поддерживается.
  • Важно учитывать, что сложные компоненты увеличивают нагрузку на рендер, поэтому количество одновременно видимых элементов должно оставаться оптимальным.

Итоговые рекомендации по работе с NVirtualList

  1. Всегда задавать высоту/ширину контейнера.
  2. Фиксированные размеры элементов повышают производительность.
  3. Для динамической высоты использовать estimated-size.
  4. Горизонтальные списки требуют horizontal и правильных CSS-стилей.
  5. Бесконечная прокрутка реализуется через событие scroll.
  6. Сложные компоненты внутри списка следует оптимизировать для снижения нагрузки.

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