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

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

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

في ما يلي مثال على قائمة تحتوي على 1,000 صف يتم عرضها باستخدام 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 مسؤولة عن ذلك.

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