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