لیست های بزرگ را با react-window مجازی سازی کنید

جداول و لیست‌های بسیار بزرگ می‌توانند عملکرد سایت شما را به میزان قابل توجهی کاهش دهند. مجازی‌سازی می‌تواند کمک کند!

react-window کتابخانه‌ای است که امکان رندر کردن کارآمد لیست‌های بزرگ را فراهم می‌کند.

چرا این مفید است؟

ممکن است مواقعی پیش بیاید که نیاز به نمایش یک جدول یا لیست بزرگ با ردیف‌های زیاد داشته باشید. بارگذاری تک تک موارد موجود در چنین لیستی می‌تواند به طور قابل توجهی بر عملکرد تأثیر بگذارد.

مجازی‌سازی لیست یا "پنجره‌ای کردن" به این مفهوم است که فقط آنچه برای کاربر قابل مشاهده است، رندر می‌شود. تعداد عناصری که در ابتدا رندر می‌شوند، زیرمجموعه بسیار کوچکی از کل لیست است و "پنجره" محتوای قابل مشاهده با ادامه اسکرول کاربر حرکت می‌کند . این امر هم عملکرد رندر و هم اسکرول لیست را بهبود می‌بخشد.

پنجره محتوا در یک لیست مجازی
جابجایی «پنجره» محتوا در یک لیست مجازی

گره‌های DOM که از «پنجره» خارج می‌شوند، بازیافت می‌شوند یا بلافاصله با عناصر جدیدتر جایگزین می‌شوند، زیرا کاربر در لیست به پایین اسکرول می‌کند. این کار تعداد تمام عناصر رندر شده را متناسب با اندازه پنجره نگه می‌دارد.

پنجره واکنش

react-window یک کتابخانه کوچک و شخص ثالث است که ایجاد لیست‌های مجازی در برنامه شما را آسان‌تر می‌کند. این کتابخانه تعدادی API پایه ارائه می‌دهد که می‌توانند برای انواع مختلف لیست‌ها و جداول مورد استفاده قرار گیرند.

چه زمانی از لیست‌های با اندازه ثابت استفاده کنیم

اگر یک لیست طولانی و تک‌بعدی از آیتم‌های با اندازه یکسان دارید، از کامپوننت 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 یک property با نام height ، width و itemSize می‌پذیرد تا اندازه آیتم‌های درون لیست را کنترل کند.
  • تابعی که ردیف‌ها را رندر می‌کند، به عنوان یک تابع فرزند به FixedSizeList ارسال می‌شود. جزئیات مربوط به آیتم خاص را می‌توان با آرگومان index ( items[index] ) مشاهده کرد.
  • یک پارامتر style نیز به متد رندرینگ row ارسال می‌شود که باید به عنصر row متصل شود. آیتم‌های لیست به طور مطلق با مقادیر ارتفاع و عرض اختصاص داده شده به صورت درون خطی، موقعیت‌یابی می‌شوند و پارامتر style مسئول این امر است.

چه زمانی از لیست‌های با اندازه متغیر استفاده کنیم؟

از کامپوننت VariableSizeList برای رندر کردن لیستی از آیتم‌ها با اندازه‌های مختلف استفاده کنید. این کامپوننت به همان روش یک لیست با اندازه ثابت کار می‌کند، اما در عوض به جای یک مقدار خاص، یک تابع برای prop 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;

تابع اندازه آیتم که به prop itemSize ارسال شده است، در این مثال ارتفاع ردیف‌ها را تصادفی می‌کند. با این حال، در یک برنامه واقعی، باید منطق واقعی وجود داشته باشد که اندازه هر آیتم را تعریف کند. در حالت ایده‌آل، این اندازه‌ها باید بر اساس داده‌ها محاسبه شوند یا از یک API به دست آیند.

شبکه‌ها

react-window همچنین از مجازی‌سازی لیست‌های چندبعدی یا گریدها پشتیبانی می‌کند. در این زمینه، «پنجره» محتوای قابل مشاهده با اسکرول افقی و عمودی کاربر تغییر می‌کند.

پنجره متحرک محتوا در یک شبکه مجازی، دو بعدی است.
جابجایی «پنجره» محتوا در یک شبکه مجازی، دو بعدی است.

به طور مشابه، هر دو کامپوننت FixedSizeGrid و VariableSizeGrid می‌توانند بسته به اینکه آیا اندازه آیتم‌های لیست خاص می‌تواند متفاوت باشد یا خیر، مورد استفاده قرار گیرند.

  • برای FixedSizeGrid ، API تقریباً مشابه است، اما با این تفاوت که ارتفاع، عرض و تعداد آیتم‌ها باید هم برای ستون‌ها و هم برای ردیف‌ها نمایش داده شوند.
  • برای VariableSizeGrid ، هم عرض ستون‌ها و هم ارتفاع ردیف‌ها را می‌توان با ارسال توابع به جای مقادیر به propهای مربوطه تغییر داد.

برای دیدن نمونه‌هایی از شبکه‌های مجازی، به مستندات نگاهی بیندازید.

بارگذاری تنبل در اسکرول

بسیاری از وب‌سایت‌ها با انتظار برای بارگذاری و رندر آیتم‌ها در یک لیست طولانی تا زمانی که کاربر به پایین اسکرول کند، عملکرد را بهبود می‌بخشند. این تکنیک که معمولاً به عنوان "بارگذاری نامحدود" شناخته می‌شود، با عبور کاربر از یک آستانه مشخص نزدیک به انتها، گره‌های DOM جدیدی را به لیست اضافه می‌کند. اگرچه این روش بهتر از بارگذاری همزمان همه آیتم‌های یک لیست است، اما اگر کاربر از آن تعداد ردیف عبور کرده باشد، همچنان DOM را با هزاران ورودی ردیف پر می‌کند. این می‌تواند منجر به حجم بیش از حد بزرگ DOM شود که با کندتر کردن محاسبات سبک و جهش‌های 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 فرزند که شامل لیست بی‌نهایت loader است، ارسال می‌شود. این مهم است زیرا loader بی‌نهایت باید یک فراخوانی مجدد برای بارگذاری موارد بیشتر، زمانی که کاربر از یک نقطه خاص عبور کرد، اجرا کند.

در اینجا نحوه 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 قرار گرفته است. props های اختصاص داده شده به loader عبارتند از:

  • isItemLoaded : متدی که بررسی می‌کند آیا یک آیتم خاص بارگذاری شده است یا خیر.
  • itemCount : تعداد آیتم‌های موجود در لیست (یا تعداد مورد انتظار)
  • loadMoreItems : فراخوانی که promiseای را برمی‌گرداند که به داده‌های اضافی برای لیست منجر می‌شود.

یک render prop برای برگرداندن تابعی که کامپوننت لیست برای رندر کردن از آن استفاده می‌کند، استفاده می‌شود. هر دو ویژگی onItemsRendered و ref ویژگی‌هایی هستند که باید ارسال شوند.

پیمایش به پایین لیست ممکن است همان حس را داشته باشد، اما اکنون درخواستی برای بازیابی 10 کاربر از یک API کاربر تصادفی هر بار که به انتهای لیست نزدیک می‌شوید، ارسال می‌شود. همه این کارها در حالی انجام می‌شود که فقط یک "پنجره" از نتایج در یک زمان ارائه می‌شود.

با بررسی index یک آیتم خاص، بسته به اینکه آیا درخواستی برای ورودی‌های جدیدتر ارسال شده است یا خیر و آیتم هنوز در حال بارگذاری است، می‌توان وضعیت بارگذاری متفاوتی را برای آن آیتم نشان داد.

برای مثال:

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

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

اسکن بیش از حد

از آنجایی که موارد موجود در یک لیست مجازی فقط با اسکرول کردن کاربر تغییر می‌کنند، ممکن است فضای خالی به طور خلاصه چشمک بزند، زیرا ورودی‌های جدیدتر در شرف نمایش هستند. می‌توانید هر یک از مثال‌های قبلی این راهنما را به سرعت اسکرول کنید تا متوجه این موضوع شوید.

برای بهبود تجربه کاربری لیست‌های مجازی، react-window به شما امکان می‌دهد تا با استفاده از ویژگی overscanCount ، آیتم‌ها را overscan کنید. این به شما امکان می‌دهد تا تعریف کنید که چه تعداد آیتم خارج از «پنجره» قابل مشاهده، در هر زمان رندر شوند.

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

overscanCount برای هر دو کامپوننت FixedSizeList و VariableSizeList کار می‌کند و مقدار پیش‌فرض آن ۱ است. بسته به بزرگی لیست و همچنین اندازه هر آیتم، اسکن بیش از یک ورودی می‌تواند از ایجاد فضای خالی قابل توجه هنگام اسکرول کردن کاربر جلوگیری کند. با این حال، اسکن بیش از حد ورودی‌ها می‌تواند بر عملکرد تأثیر منفی بگذارد. کل نکته استفاده از یک لیست مجازی، به حداقل رساندن تعداد ورودی‌ها به مقداری است که کاربر می‌تواند در هر لحظه ببیند، بنابراین سعی کنید تعداد آیتم‌های اسکن شده را تا حد امکان کم نگه دارید.

برای FixedSizeGrid و VariableSizeGrid ، از ویژگی‌های overscanColumnsCount و overscanRowsCount به ترتیب برای کنترل تعداد ستون‌ها و ردیف‌هایی که باید overscan شوند، استفاده کنید.

نتیجه‌گیری

اگر مطمئن نیستید که از کجا مجازی‌سازی لیست‌ها و جداول را در برنامه خود شروع کنید، این مراحل را دنبال کنید:

  1. اندازه‌گیری عملکرد رندر و پیمایش. این مقاله نشان می‌دهد که چگونه می‌توان از شاخص FPS در Chrome DevTools برای بررسی میزان کارایی رندر شدن آیتم‌ها در یک لیست استفاده کرد.
  2. برای هر لیست یا شبکه طولانی که بر عملکرد تأثیر می‌گذارد react-window را لحاظ کنید.
  3. اگر ویژگی‌های خاصی در react-window پشتیبانی نمی‌شوند، و نمی‌توانید خودتان این قابلیت را اضافه کنید، استفاده از react-virtualized را در نظر بگیرید.
  4. اگر نیاز دارید که آیتم‌ها را همزمان با اسکرول کردن کاربر، به صورت تنبل بارگذاری کنید، لیست مجازی‌سازی‌شده خود را با react-window-infinite-loader پوشش دهید.
  5. از ویژگی overscanCount برای لیست‌های خود و از ویژگی‌های overscanColumnsCount و overscanRowsCount برای گریدها استفاده کنید تا از خالی شدن ناگهانی محتوا جلوگیری شود. ورودی‌های زیادی را overscan نکنید زیرا این امر بر عملکرد تأثیر منفی خواهد گذاشت.