@akhatmirzo/ui

VirtualList

Katta ro'yxatlar uchun: 10 000+ qator qotmasdan, chunki faqat ko'rinadigan qatorlar chiziladi.

#1InfoFoydalanuvchi tizimga kirdi
#2TayyorTo'lov qabul qilindi
#3OgohlantirishOmborda mahsulot kam qoldi
#4XatoSMS yuborib bo'lmadi
#5InfoFoydalanuvchi tizimga kirdi
#6TayyorTo'lov qabul qilindi
#7OgohlantirishOmborda mahsulot kam qoldi
#8XatoSMS yuborib bo'lmadi
#9InfoFoydalanuvchi tizimga kirdi
#10TayyorTo'lov qabul qilindi
#11OgohlantirishOmborda mahsulot kam qoldi
#12XatoSMS yuborib bo'lmadi
#13InfoFoydalanuvchi tizimga kirdi
#14TayyorTo'lov qabul qilindi
#15OgohlantirishOmborda mahsulot kam qoldi
#16XatoSMS yuborib bo'lmadi
#17InfoFoydalanuvchi tizimga kirdi

Import

import { VirtualList, type VirtualListHandle } from '@akhatmirzo/ui';

Yuqoridagi ro'yxatda 10 000 ta yozuv, lekin DOM'da bir vaqtda faqat 20 ga yaqin qator bor: ko'rinib turganlar va ikki tomonda bir oz zaxira (overscan). Scroll paytida qatorlar qayta ishlatiladi (TanStack Virtual asosida).

Qachon ishlatish

  • Yuzlab va minglab elementli ro'yxatlar — jurnal (log), xabarlar, bildirishnomalar, katalog. 100 tagacha element bo'lsa, oddiy ro'yxat yetarli va soddaroq.
  • Cheksiz yuklash (infinite scroll) — onEndReached va loadingMore bilan, sahifalash tugmalarisiz.
  • Jadval kerak bo'lsa — ustunlar, saralash, filtr bilan — DataTable ning virtual rejimini ishlating.
  • Balandlikni aniq bering: height majburiy — virtual ro'yxat o'z scroll konteyneriga ega bo'lishi kerak.
  • Har bir qatorga barqaror kalit bering (getItemKey) — ro'yxat o'zgarganda (qo'shish, o'chirish) qatorlar adashmaydi.

Qatorga o'tish

ref — DOM element emas, boshqaruv obyekti (VirtualListHandle): scrollToIndex, scrollToOffset, measure va scroll konteyneri — element. Yuqoridagi demoda raqam yozib, Enter bosing.

const ref = useRef<VirtualListHandle>(null);

ref.current?.scrollToIndex(4999, { align: 'start' }); // 5000-qator tepada
ref.current?.scrollToOffset(0); // boshiga
ref.current?.element?.focus(); // DOM element

Har xil balandlik

Qatorlar balandligi har xil bo'lsa (xabarlar, izohlar) — dynamic bering: har bir qator chizilgach haqiqiy o'lchami olinadi. estimateSize bu holda faqat boshlang'ich taxmin; u haqiqiy o'rtachaga qanchalik yaqin bo'lsa, scroll paneli shunchalik silliq.

Aziza Karimova

Assalomu alaykum!

Malika Yusupova

Yetkazish manzilini o'zgartirsam bo'ladimi? Hozir Samarqanddaman, kuryer oldindan qo'ng'iroq qilsin.

G'ayrat Usmonov

Rahmat, mahsulot juda yoqdi. Qadoqlash ham a'lo darajada edi, do'stlarimga ham tavsiya qilaman.

Bobur Toshmatov

Buyurtmam qachon yetib keladi? Kecha to'lov qilgan edim.

Aziza Karimova

Rahmat, mahsulot juda yoqdi. Qadoqlash ham a'lo darajada edi, do'stlarimga ham tavsiya qilaman.

Malika Yusupova

Buyurtmam qachon yetib keladi? Kecha to'lov qilgan edim.

G'ayrat Usmonov

Ha, albatta.

Bobur Toshmatov

Assalomu alaykum!

Aziza Karimova

Ha, albatta.

Malika Yusupova

Assalomu alaykum!

G'ayrat Usmonov

Yetkazish manzilini o'zgartirsam bo'ladimi? Hozir Samarqanddaman, kuryer oldindan qo'ng'iroq qilsin.

Balandlik qatorning o'zida bo'lsin

dynamic siz qatorga estimateSize balandligi beriladi — qator ichidagi element ham shu balandlikda bo'lsin (h-12 = 48px). gap qatorlar orasidagi masofani beradi; margin ishlatmang — u o'lchovga kirmaydi.

Cheksiz yuklash

onEndReached — oxiriga endReachedThreshold (standart 5) qator qolganda chaqiriladi, har bir ro'yxat uzunligi uchun bir marta. Yuklash paytida loadingMore bering: pastda Spinner qatori chiqadi va onEndReached qayta chaqirilmaydi. Agar yuklash xato bilan tugasa (loadingMore false bo'ldi, lekin uzunlik o'zgarmadi), keyingi scroll'da yana chaqiriladi — qayta urinish o'zi bo'ladi.

Yuklangan: 40 / 400

№1Malika Rahimova1 485 000 so'm
№2Nodira Saidova1 470 000 so'm
№3Otabek Qodirov1 455 000 so'm
№4Jasur Karimov1 440 000 so'm
№5Malika Rahimova1 425 000 so'm
№6Nodira Saidova1 410 000 so'm
№7Otabek Qodirov1 395 000 so'm
№8Jasur Karimov1 380 000 so'm
№9Malika Rahimova1 365 000 so'm
№10Nodira Saidova1 350 000 so'm
№11Otabek Qodirov1 335 000 so'm
№12Jasur Karimov1 320 000 so'm

Bo'sh holat

items bo'sh bo'lsa, emptyState (standart — "Ro'yxat bo'sh") ko'rsatiladi.

Hali bildirishnomalar yo'q

Props

Qolgan <div> atributlarini qabul qiladi (scroll konteyneriga). aria-label / aria-labelledby ichki ro'yxat elementiga (role="list") beriladi.

PropTipStandartTavsif
itemsmajburiyreadonly T[]—Elementlar
renderItemmajburiy(item: T, index: number) => ReactNode—Qatorni chizish
estimateSizemajburiynumber | ((index: number) => number)—Taxminiy qator balandligi, px
heightmajburiynumber | string—Konteyner balandligi: px yoki CSS qiymat ("60vh")
overscannumber5Ekrandan tashqarida qo'shimcha chiziladigan qatorlar
dynamicbooleanfalseQatorlar har xil balandlikda — o'lchab olinadi
getItemKey(item: T, index: number) => string | number—Qator kaliti (standart — indeks)
gapnumber0Qatorlar orasidagi masofa, px
onEndReached() => void—Oxiriga yaqinlashganda (har uzunlik uchun bir marta)
endReachedThresholdnumber5Necha qator qolganda
loadingMorebooleanfalsePastda Spinner; bu paytda onEndReached chaqirilmaydi
emptyStateReactNode—Bo'sh ro'yxat ko'rinishi
onScrollComponentProps<'div'>['onScroll']—
labels{ loadingMore?: string; empty?: ReactNode }{ loadingMore: 'Yuklanmoqda...', empty: "Ro'yxat bo'sh" }
listClassNamestring—role="list" elementi klassi
refRef<VirtualListHandle>—Boshqaruv obyekti; DOM — handle.element

VirtualListHandle

PropTipStandartTavsif
elementHTMLDivElement | null—Scroll konteyneri
scrollToIndex(index: number, options?: ScrollToOptions) => void—options: { align?: 'start' | 'center' | 'end' | 'auto', behavior? }
scrollToOffset(offset: number, options?: ScrollToOptions) => void—
measure() => void—O'lchamlar keshini tozalab, qatorlarni qayta o'lchaydi

Klaviatura

Scroll konteyneri Tab bilan fokuslanadi (fokus halqasi ko'rinadi). Fokusda brauzerning odatiy scroll tugmalari ishlaydi: ↑ ↓, PageUp / PageDown, Home / End, Space.

Accessibility

  • Ro'yxat semantikasi: ichki element — role="list", har bir qator — role="listitem". aria-setsize (jami) va aria-posinset (o'rni) bilan — ekran o'quvchi "10 000 dan 5 000-element" deb o'qiydi, garchi DOM'da 20 ta qator bo'lsa ham.
  • Nom bering: aria-label yoki aria-labelledby — ro'yxatga beriladi.
  • Yuklanish: loadingMore paytida ro'yxatda aria-busy, Spinner'ning yashirin matni (labels.loadingMore) o'qiladi.
  • Qator ichidagi interaktiv elementlar (tugma, havola) odatdagidek ishlaydi, lekin ekrandan tashqaridagi qatorlar DOM'da yo'q — sahifa ichidagi qidiruv (Ctrl+F) ularni topmaydi. Bunday holatda o'z qidiruv maydoningizni qo'shing.

Sahifada