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

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

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

في ما يلي مثال على قائمة تحتوي على 1,000 صف يتم عرضها باستخدام react-window. يُرجى محاولة الانتقال للأسفل بأسرع ما يمكن.

لماذا يُعدّ هذا الردّ مفيدًا؟

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

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

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

تتم إعادة تدوير عُقد DOM التي تخرج من "النافذة" أو استبدالها على الفور بعناصر أحدث أثناء انتقال المستخدم لأسفل القائمة. يحافظ هذا على عدد جميع العناصر المعروضة خاصة بحجم النافذة.

نافذة التفاعل

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