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

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

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

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