Infinite Scroll — это техника динамической подгрузки данных при прокрутке, которая позволяет отображать большое количество элементов без необходимости загружать их все сразу. В библиотеке Naive UI эта функциональность реализуется через компонент NList с сочетанием scroll events и реактивного состояния Vue.
NList и обработка событий прокруткиКомпонент NList в Naive UI поддерживает стандартные события скролла. Для реализации бесконечной прокрутки создаются реактивные массивы данных и функции, которые подгружают новые элементы при достижении конца списка.
Пример базовой структуры:
<template>
<n-list
:data="items"
:virtual-scroll="true"
@scroll="handleScroll"
>
<template #default="{ item }">
<n-list-item>{{ item.name }}</n-list-item>
</template>
</n-list>
</template>
<script setup>
import { ref } from 'vue'
const items = ref(Array.from({ length: 20 }, (_, i) => ({ name: `Элемент ${i + 1}` })))
function handleScroll(event) {
const scrollContainer = event.target
if (scrollContainer.scrollTop + scrollContainer.clientHeight >= scrollContainer.scrollHeight) {
loadMore()
}
}
function loadMore() {
const nextItems = Array.from({ length: 10 }, (_, i) => ({ name: `Элемент ${items.value.length + i + 1}` }))
items.value.push(...nextItems)
}
</script>
virtual-scroll обеспечивает рендер
только видимых элементов, что значительно увеличивает производительность
при работе с большими списками.scroll позволяет отслеживать
положение скролла и инициировать подгрузку новых данных при достижении
нижней границы контейнера.Использование виртуального скролла Для списков с
сотнями или тысячами элементов обязательно включать
virtual-scroll, иначе рендер всех элементов приведёт к
торможению интерфейса.
Дебаунс события прокрутки Частые события скролла могут вызывать множественные вызовы функции подгрузки. Оптимально использовать debounce:
import { debounce } from 'lodash'
const handleScroll = debounce((event) => {
const scrollContainer = event.target
if (scrollContainer.scrollTop + scrollContainer.clientHeight >= scrollContainer.scrollHeight) {
loadMore()
}
}, 200)
loadMore при медленном отклике сервера используют
флаг loading:const loading = ref(false)
async function loadMore() {
if (loading.value) return
loading.value = true
const nextItems = await fetchNextItems()
items.value.push(...nextItems)
loading.value = false
}
Infinite Scroll часто используется с API. В Naive UI комбинация
реактивных массивов и async/await позволяет элегантно
подгружать данные:
async function fetchNextItems() {
const response = await fetch(`/api/items?offset=${items.value.length}&limit=20`)
const data = await response.json()
return data.map(item => ({ name: item.title }))
}
offset и limit), иначе список может
бесконтрольно расти.Для улучшения UX можно использовать компоненты NSpin и NEmpty:
<n-list :data="items" @scroll="handleScroll">
<template #default="{ item }">
<n-list-item>{{ item.name }}</n-list-item>
</template>
<template #footer>
<n-spin v-if="loading" size="small" />
<n-empty v-else-if="items.length === 0" description="Список пуст" />
</template>
</n-list>
#footer позволяет отобразить индикатор
загрузки прямо внизу списка.NEmpty сообщает пользователю о том,
что данных пока нет.Важно учитывать сценарии, когда сервер возвращает пустой массив или
происходит ошибка. Для этого вводят флаг hasMore:
const hasMore = ref(true)
async function loadMore() {
if (!hasMore.value || loading.value) return
loading.value = true
try {
const nextItems = await fetchNextItems()
if (nextItems.length === 0) hasMore.value = false
items.value.push(...nextItems)
} catch (e) {
console.error('Ошибка при подгрузке:', e)
} finally {
loading.value = false
}
}
hasMore предотвращает лишние
запросы, когда данные закончились.При использовании Infinite Scroll вместе с фильтрами или сортировкой следует:
items при изменении фильтра.offset или номер страницы.watch(filter, async () => {
items.value = []
offset.value = 0
hasMore.value = true
await loadMore()
})
Это гарантирует корректное отображение данных при изменении условий запроса.
На мобильных важно учитывать:
NListItem)IntersectionObserver вместо
scroll для более плавного UX и меньшей нагрузки на
рендер.const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) loadMore()
}, { threshold: 1.0 })
onMounted(() => {
observer.observe(document.querySelector('#list-end-marker'))
})
#list-end-marker размещается
внизу списка и служит триггером подгрузки новых элементов.Infinite Scroll в Naive UI реализуется через комбинацию реактивного состояния, компонентов NList, виртуального скролла и отслеживания позиции скролла. Для производительного и удобного интерфейса рекомендуется:
virtual-scroll для больших списков;debounce и флаги загрузки;IntersectionObserver на мобильных для плавной
подгрузки.Такая архитектура обеспечивает плавную и масштабируемую реализацию бесконечной прокрутки для любых приложений на Vue 3 с использованием Naive UI.