Cara menggunakan TanStack Virtual untuk mempercepat daftar panjang

 – Beragampengetahuan
12 mins read

Cara menggunakan TanStack Virtual untuk mempercepat daftar panjang – Beragampengetahuan

Saat Anda membuat umpan sosial, jaringan data, atau UI obrolan, semuanya terasa nyaman dengan 10 proyek tiruan. Kemudian Anda terhubung ke API nyata dan merender 50.000 baris myList.map(...)dan browser terkunci.

Cara menggunakan TanStack Virtual untuk mempercepat daftar panjang

Masalah intinya sederhana: Anda meminta DOM melakukan terlalu banyak pekerjaan.

Virtualisasi memecahkan masalah ini dengan hanya menampilkan apa yang sebenarnya dilihat pengguna. Daripada memasang 50.000 node, Anda membuat 15-20 item terlihat di area pandang dan buffer kecil. Browser kini hanya dapat mengelola beberapa lusin elemen dalam satu waktu.

TanStack Virtual melakukan pekerjaan berat untuk model ini. Ini adalah utilitas virtualisasi tanpa kepala modern: menangani matematika bergulir, penghitungan ukuran, dan pemosisian item, sementara Anda memiliki kendali penuh atas markup dan gaya.

Dalam artikel ini, Anda akan membuat feed live chat yang realistis dan berkinerja tinggi dari awal. Umpan ini akan:

  • Render ribuan pesan
  • Mendukung tinggi baris dinamis (pesan panjang dan pesan pendek)
  • Muat riwayat lama saat menggulir ke atas (membalikkan gulir tak terbatas)

Contents

🚀 Berlangganan Buletin Putar Ulang

memutar ulang adalah buletin mingguan untuk para pemimpin pengembangan dan teknik.

Diterbitkan setiap minggu, ini adalah panduan pilihan Anda untuk percakapan paling penting tentang pengembangan front-end, alat AI yang sedang berkembang, dan keadaan perangkat lunak modern.

Pengaturan proyek

Mulai proyek React + TypeScript baru menggunakan Vite:

npx create-vite@latest tanstack-virtual-chat --template react-ts
cd tanstack-virtual-chat

Anda hanya memerlukan dua dependensi tambahan:

  1. @tanstack/react-virtual untuk virtualisasi
  2. @faker-js/faker Untuk menghasilkan kumpulan data yang besar dan realistis

Instal mereka:

npm install @tanstack/react-virtual @faker-js/faker

Kemudian mulai server pengembangan:

npm run dev

Anda akan melihat halaman awal Vite + React default.

Tentukan wadah gulir

Virtualisasi tidak akan berfungsi kecuali virtualizer mengetahui area pandang mana yang menjadi tanggung jawabnya. Ini berarti Anda harus menyediakan:

  • wadah bergulir dengan ketinggian tetap
  • gaya overflow (biasanya overflow-y: auto)

Ganti konten src/App.css Dan:

/* src/App.css */
body 
  font-family: sans-serif;
  padding: 2rem;
  display: grid;
  place-items: center;
  min-height: 100vh;


.chat-container 
  /* You MUST provide a defined height. */
  height: 600px;
  width: 400px;

  /* You MUST provide an overflow property. */
  overflow-y: auto;

  border: 1px solid #ccc;
  border-radius: 8px;


.chat-bubble 
  padding: 12px 16px;
  border-bottom: 1px solid #eee;
  display: flex;
  gap: 8px;


.chat-bubble strong 
  color: #333;
  min-width: 70px;


.chat-bubble p 
  margin: 0;
  color: #555;
  /* Allow long messages to wrap */
  word-break: break-word;

Anda dapat menghapus gaya default src/index.css.

Selanjutnya, siapkan shell viewport sederhana src/App.tsx:

// src/App.tsx
import './App.css'

function App() 
  return (
    <div>
      <h1>Livestream Chat Feed</h1>
      /* This div is our scrollable viewport */
      <div className="chat-container">
        /* We will render our virtualized list here */
      </div>
    </div>
  )


export default App

Pada titik ini, browser akan menampilkan judul dan kotak kosong 600 piksel. Ini adalah area pandang yang akan digunakan mesin virtual.

Hasilkan kumpulan data yang besar

Untuk menunjukkan masalah kinerja, Anda memerlukan pesan dalam jumlah besar. Gunakan Faker untuk menghasilkan 10.000 pesan obrolan.

membuat src/utils.ts:

// src/utils.ts
import  faker  from '@faker-js/faker'

export type ChatMessage = 
  id: string
  author: string
  message: string


const createRandomMessage = (): ChatMessage => 
  return 
    id: faker.string.uuid(),
    author: faker.person.firstName(),
    message: faker.lorem.sentences( min: 1, max: 15 ),
  


// Create a massive list of 10,000 messages
export const allMessages = Array.from( length: 10_000 , createRandomMessage)

Anda sekarang memiliki:

  • antarmuka pengguna cangkang
  • Gaya dasar
  • satu allMessages Array berisi 10.000 item

Selanjutnya, Anda akan mempelajari mengapa Anda memerlukan virtualisasi.

Menerapkan mesin virtual

Langkah 1: Render semuanya (dan hancurkan)

Pertama, render seluruh daftar App.tsx Gunakan secara teratur .map():

// src/App.tsx
import './App.css'
import  allMessages  from './utils'

function App() 
  return (
    <div>
      <h1>Livestream Chat Feed</h1>
      <div className="chat-container">
        allMessages.map((msg) => (
          <div key=msg.id className="chat-bubble">
            <strong>msg.author</strong>
            <p>msg.message</p>
          </div>
        ))
      </div>
    </div>
  )


export default App

Simpan dan muat ulang. Browser mungkin terhenti, mogok, atau setidaknya memerlukan waktu lama untuk merespons. Anda mencoba membuat 10.000 simpul DOM sekaligus.

Ini adalah garis dasar yang Anda optimalkan.

Langkah 2: Pengkabelan useVirtualizer

Sekarang integrasikan TanStack Virtual dan sambungkan ke rolling container Anda.



// src/App.tsx
import './App.css'
import React from 'react'
import  allMessages  from './utils'
import  useVirtualizer  from '@tanstack/react-virtual'

function App()  null>(null)

  const rowVirtualizer = useVirtualizer(
    count: allMessages.length,                 // Total number of items
    getScrollElement: () => parentRef.current, // The scrolling element
    estimateSize: () => 50,                    // Approximate row height
  )

  return (
    <div>
      <h1>Livestream Chat Feed</h1>
      <div ref=parentRef className="chat-container">
        /* We will render the virtual items here */
      </div>
    </div>
  )


export default App

Anda sekarang telah memberi tahu mesin virtual:

  • Berapa banyak proyek yang ada
  • Elemen mana yang dapat di-scroll
  • Perkiraan tinggi awal untuk setiap baris

Langkah 3: Render item virtual saja

TanStack Virtual tidak memiliki kepala. Ini memberi Anda:

  1. satu getTotalSize() Fungsi yang mengembalikan tinggi daftar penuh (untuk scrollbar)
  2. satu getVirtualItems() Array yang mendeskripsikan baris yang terlihat saat ini

Untuk menggunakannya Anda memerlukan dua aturan CSS:

  • Sebuah “Pengukur” <div> Dan position: relative Dan height diatur ke getTotalSize()
  • Anak-anak yang diposisikan secara mutlak menggunakan terjemahan vertikal virtualItem.start

Ini adalah versi lengkap App.tsx Hubungkan virtualisasi dasar:

// src/App.tsx
import './App.css'
import React from 'react'
import  allMessages  from './utils'
import  useVirtualizer  from '@tanstack/react-virtual'

function App() {
  const parentRef = React.useRef<HTMLDivElement | null>(null)

  const rowVirtualizer = useVirtualizer(
    count: allMessages.length,
    getScrollElement: () => parentRef.current,
    // A more realistic estimate for our chat bubbles
    estimateSize: () => 88,
  )

  const virtualItems = rowVirtualizer.getVirtualItems()

  return (
    <div>
      <h1>Livestream Chat Feed</h1>
      <div ref=parentRef className="chat-container">
        /* Sizer div */
        <div
          style=
            height: `$rowVirtualizer.getTotalSize()px`,
            width: '100%',
            position: 'relative',
          
        >
          /* Only render the virtual items */
          virtualItems.map((virtualItem) => 
            const message = allMessages[virtualItem.index]

            return (
              <div
                key=message.id
                style=
                  position: 'absolute',
                  top: 0,
                  left: 0,
                  width: '100%',
                  transform: `translateY($virtualItem.startpx)`,
                
              >
                <div className="chat-bubble">
                  <strong>message.author</strong>
                  <p>message.message</p>
                </div>
              </div>
            )
          )
        </div>
      </div>
    </div>
  )
}

export default App

Muat ulang halaman. Menggulir 10.000 item kini akan terasa instan dan lancar karena DOM hanya berisi item yang terlihat.

Ada juga masalah: semua baris dianggap memiliki perkiraan ketinggian yang sama, sehingga pesan yang panjang terpotong.

Menangani ketinggian baris dinamis

Sekarang Anda memberi tahu mesin virtual bahwa setiap baris berada 88px Tinggi:

estimateSize: () => 88

Dalam umpan obrolan nyata, panjang dan tinggi pesan sangat bervariasi. Untuk mengatasi masalah ini, TanStack Virtual dapat mengukur setiap node setelah rendering.

Anda memerlukan dua hal:

  1. satu measureElement fungsi diteruskan ke useVirtualizer
  2. satu ref Dan data-index pada setiap elemen yang diukur

memperbarui App.tsx:

// src/App.tsx
import './App.css'
import React from 'react'
import  allMessages  from './utils'
import  useVirtualizer  from '@tanstack/react-virtual'

function App() {
  const parentRef = React.useRef<HTMLDivElement | null>(null)

  const rowVirtualizer = useVirtualizer(
    count: allMessages.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 88,
    measureElement: (element) => element.getBoundingClientRect().height,
  )

  const virtualItems = rowVirtualizer.getVirtualItems()

  return (
    <div>
      <h1>Livestream Chat Feed</h1>
      <div ref=parentRef className="chat-container">
        <div
          style=
            height: `$rowVirtualizer.getTotalSize()px`,
            width: '100%',
            position: 'relative',
          
        >
          virtualItems.map((virtualItem) => 
            const message = allMessages[virtualItem.index]

            return (
              <div
                key=message.id
                data-index=virtualItem.index
                ref=rowVirtualizer.measureElement
                style=
                  position: 'absolute',
                  top: 0,
                  left: 0,
                  width: '100%',
                  transform: `translateY($virtualItem.startpx)`,
                
              >
                <div className="chat-bubble">
                  <strong>message.author</strong>
                  <p>message.message</p>
                </div>
              </div>
            )
          )
        </div>
      </div>
    </div>
  )
}

export default App

Setiap baris sekarang diukur setelah rendering, dan virtualizer memperbarui peta dimensi internalnya. Daftar tetap lancar, sementara pesan panjang dan pendek ditempatkan dengan benar.

Umpan obrolan tervirtualisasi menampilkan gelembung obrolan berukuran dinamis

Tambahkan pemuatan tak terbatas gaya obrolan

Langkah terakhir adalah beralih dari daftar statis ke pengalaman obrolan nyata:

  • Anda awalnya hanya memuat berita terbaru
  • Saat pengguna menggulir ke atas, Anda mendapatkan riwayat yang lebih lama
  • Pada pemuatan awal, tampilan mulai bergulir ke bawah

Ini melibatkan tiga perubahan utama:

  1. merawat allMessages Bertindak sebagai database dan menjaga keadaan jendela pesan
  2. Tirukan API “dapatkan lebih banyak”, dengan menambahkan pesan lama ke depan
  3. Baris pemuatan terintegrasi dan gulir awal ke efek bawah

Langkah 1: Status pesan dan metadata paging

Refaktorisasi App Dengan cara ini Anda hanya menyimpan status 100 pesan terbaru, dan beberapa metadata tentang pemuatan:

// src/App.tsx
import './App.css'
import React from 'react'
import  allMessages, type ChatMessage  from './utils'
import  useVirtualizer  from '@tanstack/react-virtual'

// Pretend this is our "database"
const dbMessages = allMessages
const LATEST_MESSAGES_COUNT = 100

function App() 
  const [messages, setMessages] = React.useState<ChatMessage[]>(
    dbMessages.slice(dbMessages.length - LATEST_MESSAGES_COUNT)
  )
  const [isFetching, setIsFetching] = React.useState(false)
  const [hasNextPage, setHasNextPage] = React.useState(
    messages.length < dbMessages.length
  )

  // More logic will go here

Langkah 2: Mock API untuk mendapatkan riwayat yang lebih lama

Selanjutnya, tambahkan fungsi yang berpura-pura menerima pesan lama. Ia menemukan pesan terlama saat ini dbMessageslalu ambil 100 yang pertama dan letakkan di depan untuk dideklarasikan.

function App() {
  const [messages, setMessages] = React.useState<ChatMessage[]>(
    dbMessages.slice(dbMessages.length - LATEST_MESSAGES_COUNT)
  )
  const [isFetching, setIsFetching] = React.useState(false)
  const [hasNextPage, setHasNextPage] = React.useState(
    messages.length < dbMessages.length
  )

  const fetchMoreMessages = React.useCallback(() => 
    if (isFetching) return

    setIsFetching(true)

    setTimeout(() => 
      const currentOldestMessage = messages[0]
      const oldestMessageIndex = dbMessages.findIndex(
        (msg) => msg.id === currentOldestMessage.id
      )

      const newOldestIndex = Math.max(0, oldestMessageIndex - 100)
      const newMessages = dbMessages.slice(newOldestIndex, oldestMessageIndex)

      setMessages((prev) => [...newMessages, ...prev])

      if (newOldestIndex === 0) 
        setHasNextPage(false)
      

      setIsFetching(false)
    , 1000)
  , [isFetching, messages])

ini setTimeout Simulasikan latensi jaringan.

Langkah 3: Integrasikan mesin virtual dengan garis beban

Sekarang sambungkan virtualizer ke model paging ini.

daftar count menjadi messages.length + 1 Jika ada halaman riwayat lain (baris tambahannya adalah pemuat). Jika tidak, itu hanya saja messages.length:

  const parentRef = React.useRef<HTMLDivElement | null>(null)
  const hasScrolledRef = React.useRef(false)

  const rowVirtualizer = useVirtualizer(
    count: hasNextPage ? messages.length + 1 : messages.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 88,
    measureElement: (element) => element.getBoundingClientRect().height,
  )

  const virtualItems = rowVirtualizer.getVirtualItems()

Langkah 4: Picu pengambilan saat pengguna menggulir ke atas

Menambahkan efek menonton baris virtual pertama yang terlihat. jika itu index Ya 0Anda telah mencapai daftar teratas. Jika masih ada halaman lagi dan Anda belum mendapatkannya, silakan hubungi fetchMoreMessages:

  React.useEffect(() => 
    const [firstItem] = virtualItems
    if (!firstItem) return

    if (firstItem.index === 0 && hasNextPage && !isFetching) 
      fetchMoreMessages()
    
  , [virtualItems, hasNextPage, isFetching, fetchMoreMessages])

Langkah 5: Gulir ke bawah pada pemuatan awal

UI obrolan defaultnya adalah berita terbaru. Gunakan efek kedua untuk menggulir ke pesan nyata terakhir satu kali saat diinstal:

  React.useEffect(() => 
    if (virtualItems.length > 0 && !hasScrolledRef.current) 
      const lastMessageIndex = hasNextPage ? messages.length : messages.length - 1
      rowVirtualizer.scrollToIndex(lastMessageIndex,  align: 'end' )
      hasScrolledRef.current = true
    
  , [virtualItems, rowVirtualizer, messages.length, hasNextPage])

Langkah 6: Render pesan dan baris pemuat

Kapan hasNextPage benar, baris maya terletak di index === 0 Dicadangkan untuk pemuat (“Memuat pesan lama…”). Pesan sebenarnya dimulai dari indeks 1.


Lebih banyak artikel bagus dari beragampengetahuan:


Anda dapat menghitung indeks pesan yang benar seperti ini:

const messageIndex = hasNextPage
  ? virtualItem.index - 1
  : virtualItem.index

Ini adalah finalnya App Komponen dengan semua bagian terhubung bersama:

// src/App.tsx
import './App.css'
import React from 'react'
import  allMessages, type ChatMessage  from './utils'
import  useVirtualizer  from '@tanstack/react-virtual'

const dbMessages = allMessages
const LATEST_MESSAGES_COUNT = 100

function App() {
  const [messages, setMessages] = React.useState<ChatMessage[]>(
    dbMessages.slice(dbMessages.length - LATEST_MESSAGES_COUNT)
  )
  const [isFetching, setIsFetching] = React.useState(false)
  const [hasNextPage, setHasNextPage] = React.useState(
    messages.length < dbMessages.length
  )

  const parentRef = React.useRef<HTMLDivElement | null>(null)
  const hasScrolledRef = React.useRef(false)

  const fetchMoreMessages = React.useCallback(() => 
    if (isFetching) return

    setIsFetching(true)

    setTimeout(() => 
      const currentOldestMessage = messages[0]
      const oldestMessageIndex = dbMessages.findIndex(
        (msg) => msg.id === currentOldestMessage.id
      )

      const newOldestIndex = Math.max(0, oldestMessageIndex - 100)
      const newMessages = dbMessages.slice(newOldestIndex, oldestMessageIndex)

      setMessages((prev) => [...newMessages, ...prev])

      if (newOldestIndex === 0) 
        setHasNextPage(false)
      

      setIsFetching(false)
    , 1000)
  , [isFetching, messages])

  const rowVirtualizer = useVirtualizer(
    count: hasNextPage ? messages.length + 1 : messages.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 88,
    measureElement: (element) => element.getBoundingClientRect().height,
  )

  const virtualItems = rowVirtualizer.getVirtualItems()

  React.useEffect(() => 
    const [firstItem] = virtualItems
    if (!firstItem) return

    if (firstItem.index === 0 && hasNextPage && !isFetching) 
      fetchMoreMessages()
    
  , [virtualItems, hasNextPage, isFetching, fetchMoreMessages])

  React.useEffect(() => 
    if (virtualItems.length > 0 && !hasScrolledRef.current) 
      const lastMessageIndex = hasNextPage ? messages.length : messages.length - 1
      rowVirtualizer.scrollToIndex(lastMessageIndex,  align: 'end' )
      hasScrolledRef.current = true
    
  , [virtualItems, rowVirtualizer, messages.length, hasNextPage])

  return (
    <div>
      <h1>Livestream Chat Feed</h1>
      <div ref=parentRef className="chat-container">
        <div
          style=
            height: `$rowVirtualizer.getTotalSize()px`,
            width: '100%',
            position: 'relative',
          
        >
          {virtualItems.map((virtualItem) => 
            const isLoaderRow = virtualItem.index === 0 && hasNextPage

            const messageIndex = hasNextPage
              ? virtualItem.index - 1
              : virtualItem.index

            const message = messages[messageIndex]

            return (
              <div
                key=isLoaderRow ? 'loader' : message?.id ?? virtualItem.index
                data-index=virtualItem.index
                ref=rowVirtualizer.measureElement
                style=
                  position: 'absolute',
                  top: 0,
                  left: 0,
                  width: '100%',
                  transform: `translateY($virtualItem.startpx)`,
                
              >
                isLoaderRow ? (
                  <div className="chat-bubble" style= textAlign: 'center' >
                    <strong>Loading older messages...</strong>
                  </div>
                ) : message ? (
                  <div className="chat-bubble">
                    <strong>message.author:</strong>
                    <p>message.message</p>
                  </div>
                ) : null
              </div>
            )
          )}
        </div>
      </div>
    </div>
  )
}

export default App

Setelah melakukan ini, Anda akan mendapatkan:

  • Performa tinggi, daftar virtualisasi
  • Tinggi baris dinamis yang akurat
  • Membalikkan gulir tak terbatas, memuat riwayat saat menggulir ke atas
  • Anchor ke posisi scroll awal berita terkini

Umpan obrolan dengan indikator gulir dan pemuatan terbalik tanpa batas

sebagai kesimpulan

Anda memulai dengan daftar besar yang akan menyebabkan browser terhenti, dan berakhir dengan ribuan baris dinamis yang disajikan dalam umpan obrolan yang bergulir mulus tanpa batas.

Pola intinya adalah:

  1. Sediakan wadah gulir dengan ketinggian tetap untuk mesin virtual overflow: auto
  2. menggunakan useVirtualizer Render hanya baris yang terlihat ke dalam wadah bagian dalam dengan ukuran yang ditentukan
  3. menggunakan measureElement Saat baris Anda memiliki tinggi dinamis
  4. Melapisi penomoran halaman dan memuat baris untuk pengguliran tanpa batas

Pendekatan ini dapat digeneralisasikan di luar obrolan. Daftar panjang, feed, atau grid apa pun di front-end dapat menggunakan mode TanStack Virtual yang sama agar tetap responsif, bahkan dalam skala besar.

rencana pengembangan website



metode pengembangan website

jelaskan beberapa rencana untuk pengembangan website, proses pengembangan website, kekuatan dan kelemahan bisnis pengembangan website
, jasa pengembangan website, tahap pengembangan website, biaya pengembangan website

#Cara #menggunakan #TanStack #Virtual #untuk #mempercepat #daftar #panjang

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *