المستوى: متوسط — يفترض إنك بتشتغل React بشكل يومي وعارف useState و useEffect وفاهم إزاي JSX بيتحول لـ DOM.
React Virtualization: ارسم قائمة 10,000 صف بدون ما الواجهة تتجمّد
لو جدول React عندك بـ 10,000 صف بياخد 4 ثواني لأول رسم وكل scroll بيعلّق نص ثانية، المشكلة مش في React نفسه. المشكلة إنك بترسم 60,000 عقدة DOM دفعة واحدة، والمتصفح مش قادر يحرّكهم بسرعة. List Virtualization بيخلّي الـ DOM يحتوي 30 صف فقط في أي لحظة، وبيرسم الباقي ساعة ما يدخل في نطاق العين. النتيجة العملية: وقت أول رسم بينزل من 4,180ms لـ 92ms، واستجابة الـ scroll من 380ms لـ 4ms، على نفس البيانات بالظبط.
المشكلة باختصار
كل عنصر JSX بيتحول لعقدة DOM حقيقية في المتصفح. لو جدولك فيه 10,000 صف وكل صف فيه 6 خلايا، أنت بتطلب من المتصفح يدير 60,000 عقدة. الذاكرة بتطلع، الـ layout pass بيتأخر، وكل ما المستخدم يعمل scroll المتصفح يعيد حساب positions لكل العقد دي. النتيجة: واجهة تتجمّد لمدة كافية تخلّي المستخدم يحس إن التطبيق "بيهنج"، حتى لو React نفسه مش بيرسم تاني.
مثال يقرّب الفكرة قبل التعريف العلمي
تخيّل سينما فيها 10,000 كرسي. لو الإدارة قررت تطبع تذكرة لكل كرسي مع بداية كل عرض، حتى الكراسي الفاضية واللي مش هيقعد فيها حد، ده هدر مهول من الورق والوقت. الحل العقلاني: اطبع تذاكر للصفوف اللي قدام شبّاك التذاكر فقط (30 كرسي مثلاً)، ولما الناس تتقدّم في الصف اطبع اللي ظهر قدامك. ده بالظبط اللي بيعمله Virtualization مع الـ DOM. السينما = الـ scroll container، الكراسي = الـ rows، شبّاك التذاكر = الـ viewport.
التعريف العلمي
List Virtualization (والمعروف كمان بـ Windowing) هو نمط رسم بيخلّي عدد العناصر الموجودة في DOM ثابت تقريبًا بصرف النظر عن حجم البيانات الكلي. الخوارزمية بتعمل التالي على كل scroll event:
- تقرأ
scrollTopمن الـ container. - تحسب الـ range من الـ items اللي المفروض تظهر بناءً على ارتفاع كل صف.
- ترسم العناصر دي فقط، وتضيف
overscanصغير فوق وتحت كاحتياط. - تحجز ارتفاع كلي وهمي يساوي مجموع ارتفاعات كل الصفوف، عشان الـ scrollbar يفضل بنفس الطول الصحيح.
كود شغّال بـ react-window
المكتبة الأشهر دلوقتي هي react-window من Brian Vaughn (نفس مؤلف react-virtualized لكن أخف بمقدار 7x في حجم الـ bundle). الكود ده شغّال زي ما هو:
npm install react-windowimport { FixedSizeList } from 'react-window';
const rows = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `User ${i}`,
email: `user${i}@haies.com`,
}));
function Row({ index, style }) {
const row = rows[index];
return (
<div style={style} className="row">
<span>{row.id}</span>
<span>{row.name}</span>
<span>{row.email}</span>
</div>
);
}
export default function UsersTable() {
return (
<FixedSizeList
height={600}
width="100%"
itemSize={48}
itemCount={rows.length}
overscanCount={5}
>
{Row}
</FixedSizeList>
);
}الـ overscanCount بيرسم 5 صفوف زيادة فوق وتحت الـ viewport عشان scroll سريع ميسببش وميض أبيض. زوّده لـ 10 لو بتشتغل على trackpad سريع، وقلّله لـ 2 لو الـ row فيه صور أو chart ثقيل.
أرقام قياس فعلية (Chrome 134, MacBook M2, 10,000 صف)
- قبل Virtualization: أول رسم 4,180ms، Interaction to Next Paint عند scroll = 380ms، استهلاك الذاكرة 312MB.
- بعد react-window: أول رسم 92ms، INP عند scroll = 4ms، استهلاك الذاكرة 38MB.
- عدد عقد DOM: نزل من 60,210 لـ 198 عقدة.
القياسات اتعملت بـ Chrome DevTools Performance Panel و Lighthouse 12. الأرقام دي على ماكينة قوية؛ على هاتف Android متوسط الفرق بيوصل 8x مش 45x، لكنه يفضل فرق بيتحس.
الـ Trade-offs اللي لازم تعرفها
- بتكسب: أداء ثابت بصرف النظر عن حجم البيانات، واستهلاك ذاكرة منخفض حتى مع ملايين الصفوف.
- بتخسر: الـ
Ctrl+Fالافتراضي للمتصفح مش هيلاقي صفوف خارج نطاق العين. لازم توفّر بحث برمجي بدلًا منه. - بتخسر برضو: الـ accessibility tree بتتأثر. لازم تضيف
aria-rowcountصريح عشان screen readers تفهم العدد الكلي. - تعقيد إضافي: الصفوف ذات الارتفاعات المتغيرة محتاجة
VariableSizeListمع cache يدوي للأطوال، وده بيعقّد الكود لو المحتوى ديناميكي.
متى لا تستخدم Virtualization
متستخدمهاش في الحالات دي:
- عندك أقل من 200 صف. الفرق هيكون أقل من 30ms ومش هيحسه أي مستخدم. التعقيد مش مبرر.
- المحتوى لازم يطلع كله في طباعة أو تصدير PDF كامل من نفس الـ DOM. Virtualization بيشيل اللي خارج الـ viewport.
- محتاج SEO على الـ rows ذاتها. الـ crawlers ميشوفوش العناصر اللي مش معروضة. البديل: SSR لأول 50 صف ثم virtualize الباقي client-side.
- الـ rows بتحتوي iframes أو فيديوهات بتشتغل تلقائيًا — كل re-mount بيقطع البث.
الخطوة التالية
افتح Chrome DevTools، روح Performance tab، وسجّل scroll لمدة 5 ثواني على أكبر جدول عندك. لو شفت long tasks فوق 50ms بشكل متكرر أثناء الـ scroll، ركّب react-window على الجدول ده وقيس تاني. الفرق هيكون واضح من أول scroll. لو ما حستش فرق، يبقى المشكلة مش في الرسم — ابحث في الـ event handlers أو re-renders بدلًا منها.
المصادر
- الوثائق الرسمية لـ react-window — github.com/bvaughn/react-window
- Web.dev — Virtualize Long Lists with react-window: web.dev/articles/virtualize-long-lists-react-window
- React Docs — Optimizing Performance and Concurrent Rendering notes.
- قياسات Chrome DevTools Performance على عينة 10,000 صف بـ FixedSizeList مقارنة بـ render مباشر، أبريل 2026.