يمكن أن تؤدي الجداول والقوائم الكبيرة للغاية إلى إبطاء أداء موقعك الإلكتروني بشكلٍ كبير. قد تساعدك الافتراضية.
react-window
هي مكتبة تتيح عرض القوائم الكبيرة بكفاءة.
في ما يلي مثال على قائمة تحتوي على 1,000 صف يتم عرضها باستخدام react-window
. يُرجى محاولة الانتقال للأسفل بأسرع ما يمكن.
لماذا يُعدّ هذا الردّ مفيدًا؟
قد تكون هناك أوقات تحتاج فيها إلى عرض جدول أو قائمة كبيرة تحتوي على العديد من الصفوف. يمكن أن يؤثر تحميل كل عنصر في هذه القائمة في الأداء بشكل كبير.
المحاكاة الافتراضية للقائمة، أو "النافذة"، هي مفهوم عرض ما هو مرئي للمستخدم فقط. عدد العناصر التي يتم عرضها في البداية هو مجموعة فرعية صغيرة جدًا من القائمة بأكملها وتتحرك "نافذة" المحتوى المرئي عندما يواصل المستخدم الانتقال. يؤدي ذلك إلى تحسين أداء العرض والتمرير في القائمة.
![نافذة محتوى في قائمة افتراضية](https://web.developers.google.cn/static/articles/virtualize-long-lists-react-window/image/window-content-a-virtua-9c3c7f0cbf9a5.jpg?authuser=5&hl=ar)
تتم إعادة تدوير عُقد 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
أيضًا إمكانية إنشاء الشبكات أو القوائم
متعددة الأبعاد الافتراضية. في هذا السياق، تتغير "نافذة" المحتوى المرئي عندما يتنقل المستخدم أفقيًا وعموديًا.
![يعد نقل نافذة المحتوى في شبكة افتراضية ثنائي الأبعاد](https://web.developers.google.cn/static/articles/virtualize-long-lists-react-window/image/moving-window-content-a-3393c2a96ff59.jpg?authuser=5&hl=ar)
وبالمثل، يمكن استخدام المكوِّنين FixedSizeGrid
وVariableSizeGrid
بناءً على ما إذا كان حجم عناصر القائمة المحددة متفاوتًا أم لا.
- بالنسبة إلى
FixedSizeGrid
، تكون واجهة برمجة التطبيقات هي نفسها تقريبًا، ولكن يجب تمثيل الارتفاعات والعرض وأعداد العناصر لكل من الأعمدة والصفوف. - بالنسبة إلى
VariableSizeGrid
، يمكن تغيير كلّ من عرض الأعمدة وارتفاعات الصفوف من خلال تمرير الدوال بدلاً من القيم إلى العناصر ذات الصلة.
ألقِ نظرة على الوثائق للاطّلاع على أمثلة على الشبكات الافتراضية.
طريقة "التحميل الكسول" عند التنقّل في الصفحة
تقوم العديد من مواقع الويب بتحسين الأداء عن طريق انتظار تحميل العناصر وعرضها في قائمة طويلة حتى ينتقل المستخدم لأسفل. تعمل هذه الطريقة، التي يُشار إليها عادةً باسم "التحميل اللا نهائي"، على إضافة عُقد DOM الجديدة إلى القائمة أثناء انتقال المستخدم فوق حدّ معيّن بالقرب من النهاية. على الرغم من أن ذلك أفضل من تحميل جميع العناصر في القائمة مرة واحدة، إلا أنه لا يزال يؤدي إلى ملء نموذج كائن المستند بآلاف من إدخالات الصفوف إذا كان المستخدم قد تجاوز هذا العدد. قد يؤدي ذلك إلى زيادة كبيرة للغاية في حجم DOM، ما يبدأ في التأثير على الأداء من خلال جعل عمليات احتساب الأنماط وتعديلات DOM أبطأ.
قد يساعد الرسم البياني التالي في تلخيص ذلك:
![الفرق في التمرير بين القائمة العادية والافتراضية](https://web.developers.google.cn/static/articles/virtualize-long-lists-react-window/image/difference-scrolling-bet-517c003019905.jpg?authuser=5&hl=ar)
والطريقة الأفضل لحلّ هذه المشكلة هي مواصلة استخدام مكتبة مثل 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
لشبكاتك، لمنع وميض من المحتوى الفارغ. لا تفرط في مسح عدد كبير جدًا من الإدخالات لأن ذلك سيؤثر على الأداء سلبًا.