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