Tepki penceresi ile büyük listeleri sanalleştirin

Çok büyük tablolar ve listeler, sitenizin performansını önemli ölçüde yavaşlatabilir. Sanallaştırma size yardımcı olabilir.

react-window, büyük listelerin verimli şekilde oluşturulmasına olanak tanıyan bir kitaplıktır.

react-window ile oluşturulan 1.000 satırlık bir liste örneğini burada bulabilirsiniz. Olabildiğince hızlı kaydırmayı deneyin.

Neden yararlıdır?

Çok sayıda satır içeren büyük bir tablo veya liste göstermeniz gereken zamanlar olabilir. Böyle bir listedeki her öğenin yüklenmesi performansı önemli ölçüde etkileyebilir.

Liste sanallaştırma veya "pencereleme", yalnızca kullanıcının görebildiği öğelerin oluşturulmasıdır. İlk başta oluşturulan öğelerin sayısı, listenin çok küçük bir alt kümesidir ve kullanıcı kaydırmaya devam ettiğinde görünür içerik "penceresi" hareket eder. Bu, listenin hem oluşturma hem de kaydırma performansını artırır.

Sanal hale getirilmiş listedeki içerik penceresi
Sanal bir listede içerik "penceresini" taşıma

"Pencere"den çıkan DOM düğümleri geri dönüştürülür veya kullanıcı listede aşağı kaydırdıkça daha yeni öğelerle hemen değiştirilir. Bu, oluşturulan tüm öğelerin sayısını pencerenin boyutuna göre belirli bir sayıda tutar.

react-window

react-window, uygulamanızda sanallaştırılmış listeler oluşturmayı kolaylaştıran küçük bir üçüncü taraf kitaplığıdır. Farklı türlerdeki listeler ve tablolar için kullanılabilecek bir dizi temel API sunar.

Sabit boyutlu listeler ne zaman kullanılır?

Aynı boyuttaki öğelerden oluşan uzun ve tek boyutlu bir listeniz varsa FixedSizeList bileşenini kullanın.

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;
  • FixedSizeList bileşeni, listedeki öğelerin boyutunu kontrol etmek için height, width ve itemSize özelliğini kabul eder.
  • Satırları oluşturma işlevi, FixedSizeList öğesine alt öğe olarak iletilir. Belirli bir öğeyle ilgili ayrıntılara index bağımsız değişkeniyle (items[index]) erişilebilir.
  • Satır oluşturma yöntemine bir style parametresi de aktarılır. Bu parametre, satır öğesine eklenmelidir. Liste öğeleri, yükseklik ve genişlik değerleri satır içi olarak atanmış şekilde mutlak konumlandırılır. Bu durumdan style parametresi sorumludur.

Bu makalenin önceki bölümlerinde gösterilen Glitch örneğinde, FixedSizeList bileşenine dair bir örnek yer almaktadır.

Değişken boyutlu listeler ne zaman kullanılır?

Farklı boyutlara sahip öğelerin listesini oluşturmak için VariableSizeList bileşenini kullanın. Bu bileşen, sabit boyutlu bir liste gibi çalışır ancak belirli bir değer yerine itemSize özelliği için bir işlev bekler.

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;

Aşağıdaki yerleştirilmiş öğede bu bileşenin bir örneği gösterilmektedir.

itemSize özelliğine iletilen öğe boyutu işlevi, bu örnekte satır yüksekliklerini rastgele hale getirir. Ancak gerçek bir uygulamada, her öğenin boyutlarını tanımlayan gerçek bir mantık olmalıdır. İdeal olarak bu boyutlar, verilere göre hesaplanmalı veya bir API'den alınmalıdır.

Izgaralar

react-window ayrıca çok boyutlu listeleri veya ızgaraları sanallaştırma desteği de sunar. Bu bağlamda, kullanıcının yatay ve dikey olarak kaydırmasıyla birlikte görünür içerik "penceresi" değişir.

Sanal bir ızgaradaki içerik penceresinin hareketi iki boyutludur
Sanal bir ızgaradaki içerik "penceresinin" taşınması iki boyutludur.

Benzer şekilde, belirli liste öğelerinin boyutu değişebiliyorsa hem FixedSizeGrid hem de VariableSizeGrid bileşenleri kullanılabilir.

  • FixedSizeGrid için API yaklaşık olarak aynıdır ancak yüksekliklerin, genişliklerin ve öğe sayılarının hem sütunlar hem de satırlar için gösterilmesi gerekir.
  • VariableSizeGrid için, sütun genişlikleri ve satır yükseklikleri, değerler yerine işlevler iletilerek değiştirilebilir.

Sanal ızgara örnekleri için belgelere göz atın.

Kaydırma sırasında geç yükleme

Birçok web sitesi, kullanıcı aşağı kaydırana kadar uzun bir listedeki öğeleri yüklemeyi ve oluşturmayı bekleyerek performansı artırır. Genellikle "sonsuz yükleme" olarak adlandırılan bu teknik, kullanıcı sonuna yakın belirli bir eşiği geçtikçe listeye yeni DOM düğümleri ekler. Bu, listedeki tüm öğeleri aynı anda yüklemekten daha iyi olsa da kullanıcı bu kadar çok öğeyi kaydırdıysa DOM'u yine de binlerce satır girişiyle doldurur. Bu durum, stil hesaplamalarını ve DOM mutasyonlarını yavaşlatarak performansı etkilemeye başlayan aşırı büyük bir DOM boyutuna yol açabilir.

Aşağıdaki diyagram bu durumu özetlemeye yardımcı olabilir:

Normal liste ile sanallaştırılmış liste arasındaki kaydırma farkı
Normal ve sanallaştırılmış liste arasındaki kaydırma farkı

Bu sorunu çözmek için en iyi yaklaşım, sayfadaki öğelerin küçük bir "penceresini" korumak için react-window gibi bir kitaplık kullanmaya devam etmek ancak kullanıcı aşağı kaydırdıkça yeni girişleri de geç yüklemektir. Ayrı bir paket olan react-window-infinite-loader, react-window ile bunu mümkün kılar.

Aşağıdaki kod parçası, üst App bileşeninde yönetilen bir durum örneğini gösterir.

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;

Sonsuz yükleyici listesini içeren bir alt ListComponent öğesine loadMore yöntemi iletilir. Bu önemlidir. Çünkü kullanıcı belirli bir noktayı geçtikten sonra daha fazla öğe yüklemek için sonsuz yükleyicinin bir geri çağırma işlemi tetiklemesi gerekir.

Listeyi oluşturan ListComponent öğesi şu şekilde görünebilir:

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;

Burada FixedSizeList bileşeni InfiniteLoader içine yerleştirilmiştir. Yükleyiciye atanan özellikler şunlardır:

  • isItemLoaded: Belirli bir öğenin yüklenip yüklenmediğini kontrol eden yöntem
  • itemCount: Listedeki (veya beklenen) öğe sayısı
  • loadMoreItems: Liste için ek verilerle sonuçlanan bir söz döndüren geri çağırma

Render prop, liste bileşeninin oluşturmak için kullandığı bir işlevi döndürmek üzere kullanılır. Hem onItemsRendered hem de ref özellikleri iletilmesi gereken özelliklerdir.

Aşağıda, sonsuz yüklemenin sanallaştırılmış bir listede nasıl çalışabileceğine dair bir örnek verilmiştir.

Listede aşağı kaydırmak aynı hissi verebilir ancak artık listenin sonuna yaklaştığınızda her seferinde rastgele kullanıcı API'sinden 10 kullanıcıyı almak için istek gönderilir. Tüm bunlar, tek seferde yalnızca tek bir sonuç "penceresi" oluşturulurken yapılır.

Belirli bir öğenin index kontrol edildiğinde, yeni girişler için istek gönderilip gönderilmediğine ve öğenin hâlâ yüklenip yüklenmediğine bağlı olarak öğe için farklı bir yükleme durumu gösterilebilir.

Örneğin:

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

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

Fazla tarama

Sanal listedeki öğeler yalnızca kullanıcı kaydırdığında değiştiğinden, yeni girişler gösterilmek üzereyken boş alan kısa süreliğine yanıp sönebilir. Bunu fark etmek için bu kılavuzdaki önceki örneklerden herhangi birini hızlıca kaydırmayı deneyebilirsiniz.

Sanal listelerin kullanıcı deneyimini iyileştirmek için react-window, öğeleri overscanCount özelliğiyle taramanıza olanak tanır. Bu sayede, görünür "pencerenin" dışındaki kaç öğenin her zaman oluşturulacağını tanımlayabilirsiniz.

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

overscanCount hem FixedSizeList hem de VariableSizeList bileşenleri için çalışır ve varsayılan değeri 1'dir. Listenin büyüklüğüne ve her öğenin boyutuna bağlı olarak, birden fazla girişi aşırı taramak, kullanıcı kaydırdığında boş alanın fark edilebilir şekilde yanıp sönmesini önlemeye yardımcı olabilir. Ancak çok fazla girişi aşırı taramak performansı olumsuz etkileyebilir. Sanal liste kullanmanın asıl amacı, giriş sayısını kullanıcının herhangi bir anda görebileceği öğelerle sınırlamaktır. Bu nedenle, fazla taranan öğelerin sayısını mümkün olduğunca düşük tutmaya çalışın.

FixedSizeGrid ve VariableSizeGrid için, sırasıyla aşırı tarama yapılacak sütun ve satır sayısını kontrol etmek üzere overscanColumnsCount ve overscanRowsCount özelliklerini kullanın.

Sonuç

Uygulamanızdaki listeleri ve tabloları nerede sanallaştırmaya başlayacağınızdan emin değilseniz şu adımları uygulayın:

  1. Oluşturma ve kaydırma performansını ölçün. Bu makalede, Chrome Geliştirici Araçları'ndaki FPS ölçer'in, öğelerin bir listede ne kadar verimli şekilde oluşturulduğunu incelemek için nasıl kullanılabileceği gösterilmektedir.
  2. Performansı etkileyen uzun listeler veya tablolar için react-window özelliğini ekleyin.
  3. react-window'da desteklenmeyen belirli özellikler varsa bu işlevleri kendiniz ekleyemiyorsanız react-virtualized'ı kullanmayı düşünebilirsiniz.
  4. Kullanıcı kaydırdıkça öğeleri geç yüklemeniz gerekiyorsa sanallaştırılmış listenizi react-window-infinite-loader ile sarmalayın.
  5. Boş içeriklerin yanıp sönmesini önlemek için listelerinizde overscanCount özelliğini, ızgaralarınızda ise overscanColumnsCount ve overscanRowsCount özelliklerini kullanın. Çok fazla girişi taramayın. Aksi takdirde performans olumsuz etkilenir.