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

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

react-window کتابخانه ای است که به لیست های بزرگ اجازه می دهد تا به طور موثر ارائه شوند.

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

مثال Glitch که قبلا در این مقاله نشان داده شد، نمونه ای از یک جزء FixedSizeList را نشان می دهد.

زمان استفاده از لیست های با اندازه متغیر

از مؤلفه 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 ، هم عرض ستون و هم ارتفاع سطر را می توان با انتقال توابع به جای مقادیر به props مربوطه تغییر داد.

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

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

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

در اینجا 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 : Callback که وعده ای را برمی گرداند که به داده های اضافی لیست حل می شود

یک پروپ رندر برای برگرداندن تابعی استفاده می شود که جزء لیست از آن برای رندر کردن استفاده می کند. هر دو ویژگی onItemsRendered و ref ویژگی هایی هستند که باید به آنها منتقل شوند.

در زیر مثالی از نحوه کار بارگذاری بی نهایت با یک لیست مجازی ارائه شده است.

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

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

مثلا:

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

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

Overscanning

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

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

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

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

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

نتیجه

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

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