Virtualisasikan daftar besar dengan jendela reaksi

Tabel dan daftar yang sangat besar dapat memperlambat performa situs Anda secara signifikan. Virtualisasi dapat membantu.

react-window adalah library yang memungkinkan daftar besar dirender secara efisien.

Mengapa hal ini berguna?

Terkadang, Anda mungkin perlu menampilkan tabel atau daftar besar yang berisi banyak baris. Memuat setiap item dalam daftar tersebut dapat memengaruhi performa secara signifikan.

Virtualisasi daftar, atau "windowing", adalah konsep untuk hanya merender konten yang terlihat oleh pengguna. Jumlah elemen yang dirender pada awalnya adalah subset yang sangat kecil dari seluruh daftar dan "jendela" konten yang terlihat bergerak saat pengguna terus men-scroll. Hal ini meningkatkan performa rendering dan scrolling daftar.

Jendela konten dalam daftar virtual
Memindahkan "jendela" konten dalam daftar tervirtualisasi

Node DOM yang keluar dari "jendela" didaur ulang, atau langsung diganti dengan elemen yang lebih baru saat pengguna men-scroll ke bawah daftar. Hal ini membuat jumlah semua elemen yang dirender khusus untuk ukuran jendela.

react-window

react-window adalah library pihak ketiga kecil yang memudahkan pembuatan daftar tervirtualisasi di aplikasi Anda. Library ini menyediakan sejumlah API dasar yang dapat digunakan untuk berbagai jenis daftar dan tabel.

Kapan harus menggunakan daftar ukuran tetap

Gunakan komponen FixedSizeList jika Anda memiliki daftar satu dimensi yang panjang dengan item berukuran sama.

import React from 'react';
import { FixedSizeList } from 'react-window';

const items = [...] // some list of items

const Row = ({ index, style }) => (
  <div style={style}>
     {/* define the row component using items[index] */}
  </div>
);

const ListComponent = () => (
  <FixedSizeList
    height={500}
    width={500}
    itemSize={120}
    itemCount={items.length}
  >
    {Row}
  </FixedSizeList>
);

export default ListComponent;
  • Komponen FixedSizeList menerima properti height, width, dan itemSize untuk mengontrol ukuran item dalam daftar.
  • Fungsi yang merender baris diteruskan sebagai turunan ke FixedSizeList. Detail tentang item tertentu dapat diakses dengan argumen index (items[index]).
  • Parameter style juga diteruskan ke metode rendering baris yang harus dilampirkan ke elemen baris. Item daftar diposisikan secara absolut dengan nilai tinggi dan lebarnya yang ditetapkan secara inline, dan parameter style bertanggung jawab untuk hal ini.

Kapan harus menggunakan daftar ukuran variabel

Gunakan komponen VariableSizeList untuk merender daftar item yang memiliki ukuran berbeda. Komponen ini berfungsi dengan cara yang sama seperti daftar ukuran tetap, tetapi mengharapkan fungsi untuk properti itemSize, bukan nilai tertentu.

import React from 'react';
import { VariableSizeList } from 'react-window';

const items = [...] // some list of items

const Row = ({ index, style }) => (
  <div style={style}>
     {/* define the row component using items[index] */}
  </div>
);

const getItemSize = index => {
  // return a size for items[index]
}

const ListComponent = () => (
  <VariableSizeList
    height={500}
    width={500}
    itemCount={items.length}
    itemSize={getItemSize}
  >
    {Row}
  </VariableSizeList>
);

export default ListComponent;

Fungsi ukuran item yang diteruskan ke properti itemSize mengacak tinggi baris dalam contoh ini. Namun, dalam aplikasi sebenarnya, harus ada logika aktual yang menentukan ukuran setiap item. Idealnya, ukuran ini harus dihitung berdasarkan data atau diperoleh dari API.

Petak

react-window juga menyediakan dukungan untuk memvirtualisasi daftar multi-dimensi, atau petak. Dalam konteks ini, "jendela" konten yang terlihat berubah saat pengguna men-scroll secara horizontal dan vertikal.

Jendela konten yang bergerak dalam petak virtual adalah dua dimensi
Memindahkan "jendela" konten dalam petak tervirtualisasi bersifat dua dimensi

Demikian pula, komponen FixedSizeGrid dan VariableSizeGrid dapat digunakan, bergantung pada apakah ukuran item daftar tertentu dapat bervariasi.

  • Untuk FixedSizeGrid, API-nya hampir sama, tetapi dengan fakta bahwa tinggi, lebar, dan jumlah item harus direpresentasikan untuk kolom dan baris.
  • Untuk VariableSizeGrid, lebar kolom dan tinggi baris dapat diubah dengan meneruskan fungsi, bukan nilai, ke properti masing-masing.

Lihat dokumentasi untuk melihat contoh petak tervirtualisasi.

Pemuatan lambat saat men-scroll

Banyak situs meningkatkan performa dengan menunggu untuk memuat dan merender item dalam daftar panjang hingga pengguna men-scroll ke bawah. Teknik ini, yang biasanya disebut sebagai "pemuatan tanpa batas", menambahkan node DOM baru ke dalam daftar saat pengguna men-scroll melewati batas tertentu yang dekat dengan akhir. Meskipun lebih baik daripada memuat semua item dalam daftar sekaligus, teknik ini tetap akan mengisi DOM dengan ribuan entri baris jika pengguna telah men-scroll melewati jumlah tersebut. Hal ini dapat menyebabkan ukuran DOM yang terlalu besar, yang mulai memengaruhi performa dengan membuat penghitungan gaya dan mutasi DOM menjadi lebih lambat.

Diagram berikut dapat membantu meringkas hal ini:

Perbedaan dalam men-scroll antara daftar biasa dan daftar virtual
Perbedaan dalam scrolling antara daftar reguler dan daftar tervirtualisasi

Pendekatan terbaik untuk mengatasi masalah ini adalah terus menggunakan library seperti react-window untuk mempertahankan "jendela" kecil elemen di halaman, tetapi juga memuat entri baru secara lambat saat pengguna men-scroll ke bawah. Paket terpisah, react-window-infinite-loader, memungkinkan hal ini dengan react-window.

Pertimbangkan kode berikut yang menunjukkan contoh status yang dikelola dalam komponen App induk.

import React, { Component } from 'react';

import ListComponent from './ListComponent';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [], // instantiate initial list here
      moreItemsLoading: false,
      hasNextPage: true
    };

    this.loadMore = this.loadMore.bind(this);
  }

  loadMore() {
   // method to fetch newer entries for the list
  }

  render() {
    const { items, moreItemsLoading, hasNextPage } = this.state;

    return (
      <ListComponent
        items={items}
        moreItemsLoading={moreItemsLoading}
        loadMore={this.loadMore}
        hasNextPage={hasNextPage}
      />
    );
  }
}

export default App;

Metode loadMore diteruskan ke ListComponent turunan yang berisi daftar pemuat tanpa batas. Hal ini penting karena pemuat tanpa batas perlu memicu callback untuk memuat lebih banyak item setelah pengguna men-scroll melewati titik tertentu.

Berikut tampilan ListComponent yang merender daftar:

import React from 'react';
import { FixedSizeList } from 'react-window';
import InfiniteLoader from "react-window-infinite-loader";

const ListComponent = ({ items, moreItemsLoading, loadMore, hasNextPage }) => {
  const Row = ({ index, style }) => (
     {/* define the row component using items[index] */}
  );

  const itemCount = hasNextPage ? items.length + 1 : items.length;

  return (
    <InfiniteLoader
      isItemLoaded={index => index < items.length}
      itemCount={itemCount}
      loadMoreItems={loadMore}
    >
      {({ onItemsRendered, ref }) => (
        <FixedSizeList
          height={500}
          width={500}
          itemCount={itemCount}
          itemSize={120}
          onItemsRendered={onItemsRendered}
          ref={ref}
        >
          {Row}
        </FixedSizeList>
      )}
  </InfiniteLoader>
  )
};

export default ListComponent;

Di sini, komponen FixedSizeList dienkapsulasi dalam InfiniteLoader. Properti yang ditetapkan ke pemuat adalah:

  • isItemLoaded: Metode yang memeriksa apakah item tertentu telah dimuat
  • itemCount: Jumlah item dalam daftar (atau yang diharapkan)
  • loadMoreItems: Callback yang menampilkan promise yang di-resolve ke data tambahan untuk daftar

Properti render digunakan untuk menampilkan fungsi yang digunakan komponen daftar untuk merender. Atribut onItemsRendered dan ref adalah atribut yang harus diteruskan.

Men-scroll ke bawah daftar mungkin terasa sama, tetapi permintaan kini dibuat untuk mengambil 10 pengguna dari API pengguna acak setiap kali Anda men-scroll mendekati akhir daftar. Semua ini dilakukan hanya dengan merender satu "jendela" hasil dalam satu waktu.

Dengan memeriksa index item tertentu, status pemuatan yang berbeda dapat ditampilkan untuk item, bergantung pada apakah permintaan telah dibuat untuk entri yang lebih baru dan item masih dimuat.

Contoh:

const Row = ({ index, style }) => {
  const itemLoading = index === items.length;

  if (itemLoading) {
      // return loading state
  } else {
      // return item
  }
};

Overscanning

Karena item dalam daftar tervirtualisasi hanya berubah saat pengguna men-scroll, ruang kosong dapat berkedip sebentar saat entri yang lebih baru akan ditampilkan. Anda dapat mencoba men-scroll dengan cepat salah satu contoh sebelumnya dalam panduan ini untuk melihatnya.

Untuk meningkatkan pengalaman pengguna daftar tervirtualisasi, react-window memungkinkan Anda melakukan overscan item dengan properti overscanCount. Hal ini memungkinkan Anda menentukan jumlah item di luar "jendela" yang terlihat untuk dirender setiap saat.

<FixedSizeList
  //...
  overscanCount={4}
>
  {...}
</FixedSizeList>

overscanCount berfungsi untuk komponen FixedSizeList dan VariableSizeList serta memiliki nilai default 1. Bergantung pada seberapa besar daftar dan ukuran setiap item, melakukan overscan lebih dari satu entri dapat membantu mencegah kedipan ruang kosong yang terlihat saat pengguna men-scroll. Namun, melakukan overscan terlalu banyak entri dapat memengaruhi performa secara negatif. Tujuan utama menggunakan daftar tervirtualisasi adalah untuk meminimalkan jumlah entri menjadi apa yang dapat dilihat pengguna pada waktu tertentu, jadi coba pertahankan jumlah item yang di-overscan serendah mungkin.

Untuk FixedSizeGrid dan VariableSizeGrid, gunakan properti overscanColumnsCount dan overscanRowsCount untuk mengontrol jumlah kolom dan baris yang akan di-overscan.

Kesimpulan

Jika Anda tidak yakin harus mulai dari mana untuk memvirtualisasi daftar dan tabel di aplikasi, ikuti langkah-langkah berikut:

  1. Ukur performa rendering dan scrolling. Artikel ini menunjukkan cara menggunakan pengukur FPS di Chrome DevTools untuk mempelajari seberapa efisien item dirender dalam daftar.
  2. Sertakan react-window untuk daftar atau petak panjang yang memengaruhi performa.
  3. Jika ada fitur tertentu yang tidak didukung di react-window, pertimbangkan untuk menggunakan react-virtualized jika Anda tidak dapat menambahkan fungsi ini sendiri.
  4. Enkapsulasi daftar tervirtualisasi dengan react-window-infinite-loader jika Anda perlu memuat item secara lambat saat pengguna men-scroll.
  5. Gunakan properti overscanCount untuk daftar dan properti overscanColumnsCount serta overscanRowsCount untuk petak guna mencegah kedipan konten kosong. Jangan melakukan overscan terlalu banyak entri karena hal ini akan memengaruhi performa secara negatif.