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

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

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

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