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.

Berikut ini contoh daftar yang berisi 1.000 baris yang dirender dengan react-window. Coba scroll secepat mungkin.

Mengapa hal ini bermanfaat?

Mungkin ada saatnya Anda perlu menampilkan tabel atau daftar besar yang berisi banyak baris. Memuat setiap item pada daftar tersebut dapat sangat memengaruhi performa.

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

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

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

jendela reaksi

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

Kapan menggunakan daftar ukuran tetap

Gunakan komponen FixedSizeList jika Anda memiliki daftar panjang satu dimensi yang berisi item yang 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 menggunakan argumen index (items[index]).
  • Parameter style juga diteruskan ke metode rendering baris yang harus dilampirkan ke elemen baris. Item daftar benar-benar diposisikan dengan nilai tinggi dan lebar yang ditetapkan inline, dan parameter style bertanggung jawab atas hal ini.

Contoh Glitch yang ditampilkan sebelumnya dalam artikel ini menunjukkan contoh komponen FixedSizeList.

Kapan harus menggunakan daftar berukuran 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;

Penyematan berikut menunjukkan contoh komponen ini.

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

Petak

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

Memindahkan jendela konten dalam petak virtual berbentuk dua dimensi
Memindahkan "jendela" konten dalam petak virtual memerlukan waktu dua dimensi

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

  • 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 propertinya masing-masing.

Lihat dokumentasi untuk melihat contoh grid virtual.

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, biasanya disebut sebagai "pemuatan tanpa batas", menambahkan node DOM baru ke dalam daftar saat pengguna men-scroll melewati batas tertentu yang mendekati bagian akhir. Meskipun cara ini lebih baik daripada memuat semua item pada daftar sekaligus, namun pada akhirnya DOM tetap terisi 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 memperlambat penghitungan gaya dan mutasi DOM.

Diagram berikut dapat membantu meringkas hal ini:

Perbedaan antara men-scroll antara daftar reguler dan tervirtualisasi
Perbedaan saat men-scroll antara daftar reguler dan tervirtualisasi

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

Pertimbangkan bagian 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 loader tanpa batas. Hal ini penting karena loader tanpa batas perlu mengaktifkan 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 digabungkan di dalam InfiniteLoader. Properti yang ditetapkan untuk loader adalah:

  • isItemLoaded: Metode yang memeriksa apakah item tertentu telah dimuat
  • itemCount: Jumlah item dalam daftar (atau 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 dirender. Atribut onItemsRendered dan ref adalah atribut yang perlu diteruskan.

Berikut adalah contoh cara kerja pemuatan tanpa batas dengan daftar virtual.

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. Ini semua dilakukan sambil hanya 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 tersebut masih dimuat.

Contoh:

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

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

Pemindaian berlebihan

Karena item dalam daftar virtual hanya berubah saat pengguna men-scroll, ruang kosong dapat berkedip sebentar saat entri baru akan ditampilkan. Anda dapat mencoba men-scroll dengan cepat contoh sebelumnya dalam panduan ini untuk melihat hal ini.

Untuk meningkatkan pengalaman pengguna daftar virtual, react-window memungkinkan Anda melakukan pemindaian item secara berlebih dengan properti overscanCount. Hal ini memungkinkan Anda menentukan jumlah item di luar "jendela" yang terlihat untuk selalu dirender.

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

overscanCount berfungsi untuk komponen FixedSizeList dan VariableSizeList dan memiliki nilai default 1. Bergantung pada seberapa besar daftar serta ukuran setiap item, melakukan pemindaian berlebih lebih dari satu entri dapat membantu mencegah flash yang terlihat pada ruang kosong saat pengguna men-scroll. Namun, overscan terlalu banyak entri dapat berdampak negatif pada performa. Inti penggunaan daftar virtual adalah meminimalkan jumlah entri terhadap apa yang dapat dilihat pengguna pada saat tertentu, jadi cobalah untuk menjaga jumlah item yang dipindai serendah mungkin.

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

Kesimpulan

Jika Anda tidak yakin harus mulai melakukan virtualisasi daftar dan tabel dalam aplikasi, ikuti langkah-langkah berikut:

  1. Mengukur performa rendering dan scroll. Artikel ini menunjukkan cara menggunakan meter FPS di Chrome DevTools untuk mempelajari seberapa efisien item dirender pada daftar.
  2. Sertakan react-window untuk daftar panjang atau petak 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. Gabungkan daftar virtual dengan react-window-infinite-loader jika Anda perlu memuat item dengan lambat saat pengguna men-scroll.
  5. Gunakan properti overscanCount untuk daftar serta properti overscanColumnsCount dan overscanRowsCount untuk petak Anda guna mencegah flash konten kosong. Jangan memindai terlalu banyak entri karena tindakan ini akan berdampak negatif pada performa.