रिएक्शन विंडो की मदद से, बड़ी सूचियों को वर्चुअल तरीके से सेट करना

बहुत बड़ी टेबल और सूचियां, आपकी साइट की परफ़ॉर्मेंस को बहुत ज़्यादा धीमा कर सकती हैं. वर्चुअलाइज़ेशन से मदद मिल सकती है!

react-window एक ऐसी लाइब्रेरी है जिसमें बड़ी सूचियों को बेहतर तरीके से रेंडर किया जा सकता है.

यहां एक सूची का उदाहरण दिया गया है, जिसमें 1,000 लाइनें हैं, जिन्हें react-window की मदद से रेंडर किया जा रहा है. जितना हो सके तेज़ी से स्क्रोल करने की कोशिश करें.

यह जानकारी काम की क्यों है?

कई बार ऐसा हो सकता है कि आपको एक बड़ी टेबल या सूची दिखानी पड़े जिसमें कई लाइनें हों. ऐसी सूची के हर एक आइटम को लोड करने से परफ़ॉर्मेंस पर काफ़ी असर पड़ सकता है.

सूची वर्चुअलाइज़ेशन या "विंडोविंग" एक ऐसा सिद्धांत है जो सिर्फ़ उस जानकारी को रेंडर करता है जो उपयोगकर्ता को दिखती है. पहली बार रेंडर किए गए एलिमेंट की संख्या पूरी सूची का बहुत छोटा सबसेट होता है. साथ ही, जब उपयोगकर्ता स्क्रोल करना जारी रखता है, तब दिखने वाले कॉन्टेंट की "विंडो" ले जाती है. इससे सूची की रेंडरिंग और स्क्रोलिंग, दोनों परफ़ॉर्मेंस बेहतर होती है.

वर्चुअल बनाई गई सूची में कॉन्टेंट की विंडो
वर्चुअलाइज़ की गई सूची में कॉन्टेंट की "विंडो" को एक जगह से दूसरी जगह ले जाना

"विंडो" से बाहर निकलने वाले डीओएम नोड को रीसाइकल किया जाता है या जब उपयोगकर्ता सूची में नीचे की ओर स्क्रोल करता है, तो उसे तुरंत नए एलिमेंट से बदल दिया जाता है. इससे रेंडर किए गए सभी एलिमेंट की संख्या, विंडो के साइज़ के हिसाब से बनी रहती है.

प्रतिक्रिया दिखाने वाली विंडो

react-window, तीसरे पक्ष की एक छोटी लाइब्रेरी है. इससे आपके ऐप्लिकेशन में वर्चुअल सूचियां बनाना आसान हो जाता है. यह ऐसे कई बेस एपीआई उपलब्ध कराता है जिनका इस्तेमाल अलग-अलग तरह की सूचियों और टेबल के लिए किया जा सकता है.

तय साइज़ की सूचियों का इस्तेमाल कब करें

अगर आपके पास एक जैसे साइज़ वाले आइटम की लंबी और एक डाइमेंशन वाली सूची है, तो FixedSizeList कॉम्पोनेंट का इस्तेमाल करें.

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 कॉम्पोनेंट height, width, और itemSize प्रॉपर्टी का इस्तेमाल करता है.
  • पंक्तियों को रेंडर करने वाला फ़ंक्शन, FixedSizeList को चाइल्ड के तौर पर पास किया जाता है. किसी खास आइटम के बारे में जानकारी को index आर्ग्युमेंट (items[index]) से ऐक्सेस किया जा सकता है.
  • style पैरामीटर, लाइन रेंडर करने के तरीके में भी पास किया जाता है. इसे लाइन एलिमेंट के साथ अटैच करना ज़रूरी है. सूची वाले आइटम पूरी तरह से अपनी ऊंचाई और चौड़ाई के मान के साथ सही जगह पर रखे गए हों, जिन्हें इनलाइन असाइन किया गया हो. इसके लिए, style पैरामीटर ज़िम्मेदार है.

इस लेख में पहले दिखाए गए Glitch उदाहरण में, FixedSizeList कॉम्पोनेंट का उदाहरण दिया गया है.

अलग-अलग साइज़ की सूचियों का इस्तेमाल कब करना चाहिए

अलग-अलग साइज़ वाले आइटम की सूची रेंडर करने के लिए, VariableSizeList कॉम्पोनेंट का इस्तेमाल करें. यह कॉम्पोनेंट, किसी तय साइज़ की सूची की तरह ही काम करता है. हालांकि, इसके बजाय वह किसी खास वैल्यू के बजाय itemSize प्रॉपर्टी के लिए फ़ंक्शन की उम्मीद करता है.

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;

नीचे दिए गए एम्बेड में इस कॉम्पोनेंट का एक उदाहरण दिखाया गया है.

itemSize को पास किया गया आइटम साइज़ फ़ंक्शन, इस उदाहरण में पंक्ति की ऊंचाई को किसी भी क्रम में दिखाता है. हालांकि, असली ऐप्लिकेशन में, हर आइटम के साइज़ को तय करने वाला असल तर्क होना चाहिए. आम तौर पर, इन साइज़ का हिसाब, डेटा के आधार पर या एपीआई से लिया जाना चाहिए.

ग्रिड

react-window, कई डाइमेंशन वाली सूचियों या ग्रिड को वर्चुअलाइज़ करने की सुविधा भी देता है. इस मामले में, जब उपयोगकर्ता हॉरिज़ॉन्टल और वर्टिकल तरीके से स्क्रोल करता है, तो कॉन्टेंट की "विंडो" बदल जाती है.

वर्चुअलाइज़्ड ग्रिड में कॉन्टेंट को एक जगह से दूसरी जगह ले जाने की प्रक्रिया दो-डाइमेंशन में होती है
वर्चुअलाइज़्ड ग्रिड में कॉन्टेंट की "विंडो" को दो डाइमेंशन में ले जाना

इसी तरह, FixedSizeGrid और VariableSizeGrid, दोनों कॉम्पोनेंट का इस्तेमाल इस आधार पर किया जा सकता है कि खास सूची में मौजूद आइटम के साइज़ में अंतर हो सकता है या नहीं.

  • FixedSizeGrid के लिए, एपीआई करीब-करीब एक जैसा होता है. हालांकि, कॉलम और लाइन, दोनों में ऊंचाई, चौड़ाई, और आइटम की संख्या दिखानी ज़रूरी है.
  • VariableSizeGrid के लिए, कॉलम की चौड़ाई और पंक्ति की ऊंचाई, दोनों को बदला जा सकता है. ऐसा करने के लिए वैल्यू के बजाय फ़ंक्शन में पास करके, उनसे जुड़े प्रॉप में वैल्यू को पास किया जा सकता है.

वर्चुअल ग्रिड के उदाहरण देखने के लिए, दस्तावेज़ देखें.

स्क्रोल करने पर लेज़ी लोडिंग

कई वेबसाइटें आइटम की परफ़ॉर्मेंस को बेहतर बनाने के लिए, आइटम को तब तक लोड और रेंडर करती हैं, जब तक उपयोगकर्ता स्क्रोल करके नीचे नहीं आता. यह तकनीक, जिसे आम तौर पर "इनफ़ाइनाइट लोडिंग" कहा जाता है, सूची में नए DOM नोड जोड़ देती है. ऐसा तब होता है, जब उपयोगकर्ता एक तय थ्रेशोल्ड को आखिर तक स्क्रोल करता है. हालांकि, यह किसी सूची में मौजूद सभी आइटम को एक साथ लोड करने से बेहतर है, लेकिन अगर उपयोगकर्ता ने कई आइटम को स्क्रोल कर लिया है, तो इससे डीओएम में हज़ारों पंक्तियों की जानकारी अपने-आप भर जाती है. इससे डीओएम साइज़ बहुत बड़ा हो जाता है. इससे स्टाइल कैलकुलेशन और डीओएम म्यूटेशन की प्रोसेस धीमी हो जाती है और परफ़ॉर्मेंस पर असर पड़ना शुरू हो जाता है.

नीचे दिए डायग्राम से इसे समझा जा सकता है:

सामान्य और वर्चुअल बनाई गई सूची के बीच स्क्रोल करने में अंतर
सामान्य और वर्चुअल सूची को स्क्रोल करने में अंतर

इस समस्या को हल करने का सबसे अच्छा तरीका यह है कि आप किसी पेज पर एलिमेंट की एक छोटी "विंडो" को बनाए रखने के लिए, react-window जैसी लाइब्रेरी का इस्तेमाल करें. साथ ही, जब उपयोगकर्ता नीचे की ओर स्क्रोल करे, तब आप नई एंट्री को लेज़ी लोड करें. एक अलग पैकेज, react-window-infinite-loader, इसे react-window के साथ संभव बनाता है.

नीचे दिए गए कोड के बारे में सोचें, जिसमें उस स्थिति का उदाहरण दिया गया है जिसे पैरंट App कॉम्पोनेंट में मैनेज किया जाता है.

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;

loadMore तरीके को चाइल्ड ListComponent में पास किया जाता है. इसमें, लोड करने वालों की इनफ़ाइनाइट सूची होती है. यह ज़रूरी है, क्योंकि जब उपयोगकर्ता किसी तय पॉइंट से स्क्रोल करके आगे बढ़ता है, तब इनफ़ाइनाइट लोडर को ज़्यादा आइटम लोड करने के लिए, कॉलबैक को ट्रिगर करना पड़ता है.

यहां बताया गया है कि सूची को रेंडर करने वाला ListComponent कैसा दिख सकता है:

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;

यहां, FixedSizeList कॉम्पोनेंट को InfiniteLoader के अंदर रैप किया जाता है. लोडर को ये प्रॉप असाइन किए जाएंगे:

  • isItemLoaded: वह तरीका जो यह जांचता है कि किसी खास आइटम के लोड हुए हैं या नहीं
  • itemCount: सूची में मौजूद आइटम की संख्या (या अनुमानित आइटम)
  • loadMoreItems: वह कॉलबैक जो सूची के लिए अतिरिक्त डेटा का प्रॉमिस दिखाता है

रेंडर प्रॉप का इस्तेमाल, उस फ़ंक्शन को लौटाने के लिए किया जाता है जिसका इस्तेमाल सूची वाला कॉम्पोनेंट, रेंडर करने के लिए करता है. onItemsRendered और ref, दोनों एट्रिब्यूट ऐसे एट्रिब्यूट हैं जिन्हें पास करना ज़रूरी है.

नीचे एक उदाहरण में बताया गया है कि वर्चुअलाइज़्ड सूची के साथ, इनफ़ाइनाइट लोडिंग की सुविधा कैसे काम कर सकती है.

सूची को नीचे की ओर स्क्रोल करने जैसा ही महसूस हो सकता है. हालांकि, अब हर बार सूची के आखिर तक स्क्रोल करने पर, रैंडम यूज़र एपीआई से 10 उपयोगकर्ताओं को वापस लाने का अनुरोध किया जाता है. ऐसा तब किया जाता है, जब एक बार में नतीजों की सिर्फ़ एक "विंडो" को रेंडर किया जाता है.

किसी आइटम के index की जांच करने से, आइटम के लोड होने की अलग-अलग स्थिति दिख सकती है. यह इस बात पर निर्भर करता है कि नई एंट्री के लिए अनुरोध किया गया है या नहीं और आइटम अब भी लोड हो रहा है या नहीं.

उदाहरण के लिए:

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

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

ओवरस्कैनिंग

वर्चुलाइज़ की गई सूची में मौजूद आइटम तब ही बदलते हैं, जब उपयोगकर्ता स्क्रोल करता है. इस वजह से, नई एंट्री थोड़ी देर के लिए खाली हो सकती हैं, क्योंकि नई एंट्री दिखने वाली हैं. इस पर ध्यान देने के लिए, इस गाइड में दिए गए पिछले किसी भी उदाहरण को तेज़ी से स्क्रोल करने की कोशिश की जा सकती है.

वर्चुअल तौर पर बनाई गई सूचियों के उपयोगकर्ता अनुभव को बेहतर बनाने के लिए, react-window आपको overscanCount प्रॉपर्टी के साथ आइटम को ओवरस्कैन करने की सुविधा देता है. इससे यह तय किया जा सकता है कि दिखने वाली "विंडो" के बाहर कितने आइटम को हर समय रेंडर करना है.

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

overscanCount, FixedSizeList और VariableSizeList दोनों कॉम्पोनेंट के लिए काम करता है. साथ ही, इसकी डिफ़ॉल्ट वैल्यू 1 होती है. सूची कितनी बड़ी है और हर आइटम के साइज़ के हिसाब से, एक से ज़्यादा एंट्री को ओवरस्कैन करने से, जब उपयोगकर्ता स्क्रोल करता है, तब खाली जगह का फ़्लैश रोकने में मदद मिलती है. हालांकि, बहुत ज़्यादा एंट्री को ओवरस्कैन करने से परफ़ॉर्मेंस पर बुरा असर पड़ सकता है. वर्चुअल तौर पर बनाई गई सूची का इस्तेमाल करने का मकसद, ऐसी एंट्री की संख्या को कम करना होता है जिन्हें उपयोगकर्ता किसी खास समय देख सकते हैं. इसलिए, कोशिश करें कि ज़्यादा स्कैन न किए गए आइटम की संख्या कम से कम रखें.

FixedSizeGrid और VariableSizeGrid के लिए, कॉलम और पंक्तियों की संख्या को कंट्रोल करने के लिए, overscanColumnsCount और overscanRowsCount प्रॉपर्टी का इस्तेमाल करें. इससे, उस कॉलम और पंक्तियों की संख्या को कंट्रोल किया जा सकेगा.

नतीजा

अगर आपको नहीं पता कि अपने ऐप्लिकेशन में सूचियों और टेबल को वर्चुअल के तौर पर कहां से शुरू करना है, तो यह तरीका अपनाएं:

  1. रेंडरिंग और स्क्रोलिंग परफ़ॉर्मेंस माप सकते हैं. इस लेख में बताया गया है कि Chrome DevTools में FPS मीटर का इस्तेमाल करके, किसी सूची में आइटम को कितने बेहतर तरीके से रेंडर किया जा सकता है.
  2. उन लंबी सूचियों या ग्रिड के लिए react-window शामिल करें जिनसे परफ़ॉर्मेंस पर असर पड़ रहा है.
  3. अगर कुछ ऐसी सुविधाएं हैं जो react-window में काम नहीं करतीं, तो अगर आप इस सुविधा को खुद नहीं जोड़ सकते, तो react-virtualized का इस्तेमाल करें.
  4. अगर आपको उपयोगकर्ता के स्क्रोल करने के दौरान लेज़ी आइटम लोड करने की ज़रूरत है, तो अपनी वर्चुअल बनाई गई सूची को react-window-infinite-loader के साथ रैप करें.
  5. अपनी सूचियों के लिए overscanCount प्रॉपर्टी और ग्रिड के लिए overscanColumnsCount और overscanRowsCount प्रॉपर्टी का इस्तेमाल करें, ताकि खाली कॉन्टेंट को फ़्लैश करने से रोका जा सके. बहुत ज़्यादा एंट्री को ओवरस्कैन न करें, क्योंकि इससे परफ़ॉर्मेंस पर बुरा असर पड़ेगा.