إنشاء قائمة افتراضية للقوائم الكبيرة باستخدام نافذة التفاعل

يمكن أن تؤدي الجداول والقوائم الكبيرة جدًا إلى إبطاء أداء موقعك الإلكتروني بشكل كبير. يمكن أن تساعدك المحاكاة الافتراضية في ذلك.

react-window هي مكتبة تتيح عرض القوائم الكبيرة بكفاءة.

لماذا هذه الميزة مفيدة؟

قد تحتاج في بعض الأحيان إلى عرض جدول أو قائمة كبيرة تحتوي على العديد من الصفوف. يمكن أن يؤثر تحميل كل عنصر في هذه القائمة بشكل كبير في الأداء.

المحاكاة الافتراضية للقوائم، أو "عرض المحتوى في النافذة الحالية"، هي مفهوم عرض المحتوى المرئي للمستخدم فقط. إنّ عدد العناصر التي يتم عرضها في البداية هو مجموعة فرعية صغيرة جدًا من القائمة الكاملة، وتتحرّك "نافذة" المحتوى المرئي عندما يواصل المستخدم التمرير. ويؤدي ذلك إلى تحسين أداء عرض القائمة والتمرير فيها.

نافذة المحتوى في قائمة افتراضية
تحريك "نافذة" المحتوى في قائمة افتراضية

تتم إعادة استخدام عقد DOM التي تخرج من "النافذة"، أو يتم استبدالها على الفور بعناصر أحدث عندما يتنقّل المستخدم نزولاً في القائمة. يؤدي ذلك إلى الحفاظ على عدد جميع العناصر المعروضة بما يتناسب مع حجم النافذة.

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 مسؤولة عن ذلك.

حالات استخدام القوائم ذات الأحجام المتغيرة

استخدِم المكوّن 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 جديدة إلى القائمة عندما يتجاوز المستخدم حدًا معيّنًا بالقرب من النهاية. على الرغم من أنّ هذا الإجراء أفضل من تحميل كل العناصر في القائمة مرة واحدة، إلا أنّه يؤدي إلى ملء نموذج 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: دالة ردّ الاتصال التي تعرض وعدًا يتم تنفيذه للحصول على بيانات إضافية للقائمة

يتم استخدام عنصر العرض لإرجاع دالة يستخدمها مكوّن القائمة من أجل العرض. السمتان 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. قياس أداء العرض والتمرير توضّح هذه المقالة كيفية استخدام مقياس عدد اللقطات في الثانية في &quot;أدوات مطوّري البرامج في Chrome&quot; لاستكشاف مدى كفاءة عرض العناصر في قائمة.
  2. أدرِج react-window لأي قوائم أو جداول طويلة تؤثر في الأداء.
  3. إذا كانت هناك ميزات معيّنة غير متاحة في react-window، ننصحك باستخدام react-virtualized إذا لم تتمكّن من إضافة هذه الوظيفة بنفسك.
  4. يمكنك تضمين قائمتك المحوَّلة إلى سلسلة باستخدام react-window-infinite-loader إذا كنت بحاجة إلى تحميل العناصر بشكل غير متزامن أثناء تنقّل المستخدم.
  5. استخدِم السمة overscanCount لقوائمك والسمتَين overscanColumnsCount وoverscanRowsCount لشبكاتك لمنع ظهور محتوى فارغ بشكل مفاجئ. لا تفحص عددًا كبيرًا جدًا من الإدخالات، لأنّ ذلك سيؤثّر سلبًا في الأداء.