وہ DOM Bottleneck جس کے بارے میں کوئی بات نہیں کرتا

تصور کریں کہ ایک سپورٹ ڈیش بورڈ دس ہزار لاگ انٹریز (log entries) لوڈ کر رہا ہے۔ یا ایک CRM جو ایک ہی اسکرول ایبل ٹیبل میں ہر کانٹیکٹ دکھانے کی کوشش کر رہا ہے۔ React میں، اسے بنانے کا کوڈ کافی بے ضرر لگتا ہے۔ آپ ایک ایرے (array) پر میپ (map) کرتے ہیں، کچھ JSX واپس کرتے ہیں، اور فریم ورک کو اپنا کام کرنے دیتے ہیں۔ ڈویلپمنٹ کے دوران سو لائنوں کے ساتھ سب کچھ ٹھیک چلتا ہے۔ پھر جب پروڈکشن ڈیٹا آتا ہے، تو پیج انتہائی سست ہو جاتا ہے۔

براؤزر سست نہیں ہو رہا۔ وہ وہی کر رہا ہے جو آپ نے اس سے کہا ہے، اور یہی مسئلہ ہے۔ ہر رو (row) ایک DOM نوڈ بن جاتی ہے۔ ہر نوڈ کو اسٹائل کیا جاتا ہے، اس کا لے آؤٹ بنایا جاتا ہے، اسے پینٹ کیا جاتا ہے، اور میموری میں ٹریک کیا جاتا ہے۔ جب آپ اسکرول کرتے ہیں، تو براؤزر صرف اس حصے کے لیے نہیں بلکہ پورے ٹری (tree) کے لیے پوزیشنز کا دوبارہ حساب لگاتا ہے جسے آپ دیکھ رہے ہیں۔ ایونٹ لسنرز (event listeners) جمع ہوتے رہتے ہیں۔ میموری میں اضافہ ہوتا ہے۔ آخر کار، مین تھریڈ (main thread) اتنا زیادہ مصروف ہو جاتا ہے کہ انٹرفیس کلکس، کی اسٹروکس، یا خود اسکرولنگ کا جواب دینا بند کر دیتا ہے۔ تکنیکی لحاظ سے ایپلی کیشن کریش نہیں ہوئی ہوتی، لیکن اس کے سامنے بیٹھے صارف کے لیے تجربہ مکمل طور پر خراب ہو چکا ہوتا ہے۔

ایسا اس لیے ہوتا ہے کیونکہ براؤزر ہر ایک ایلیمنٹ کو ایک ساتھ ایکٹیو میموری میں رکھنے کی کوشش کرتا ہے۔ React آپ کے UI کے ورچوئل ڈسکرپشنز بنانے میں تو ماہر ہو سکتا ہے، لیکن ایک بار جب وہ ڈسکرپشنز دستاویز میں حقیقی نوڈز بن جاتے ہیں، تو ان کی قیمت بھی ہاتھ سے لکھی گئی HTML جتنی ہی ہوتی ہے۔ فریم ورک کے اندر اس سے بچنے کا کوئی راستہ نہیں ہے۔ آپ کو اس طریقے میں ساختی تبدیلی لانی ہوگی جس سے آپ لسٹ کو DOM کو فراہم کرتے ہیں۔

ورچوئلائزیشن (Virtualization) کا اصل مطلب کیا ہے

ورچوئلائزیشن وہی ساختی تبدیلی ہے۔ پورے ایرے کو رینڈر کرنے کے لیے React سے کہنے کے بجائے، آپ صرف ان آئٹمز کو رینڈر کرتے ہیں جو ویو پورٹ (viewport) کے اندر سما سکتے ہیں، ساتھ ہی اوپر اور نیچے ایک چھوٹا سا بفر (buffer) بھی۔ جیسے جیسے صارف اسکرول کرتا ہے، ایپلی کیشن ان نوڈز کو ختم کر دیتی ہے جو نظر سے باہر ہو جاتے ہیں اور دوسری طرف سے آنے والے نئے نوڈز کو انسٹینشئیٹ (instantiate) کرتی ہے۔ صارف کے لیے، یہ اب بھی ایک مسلسل لسٹ کی طرح محسوس ہوتا ہے کیونکہ کل اسکرول ایبل ہائٹ برقرار رکھی جاتی ہے، جو عام طور پر ایک ہی اونچے کنٹینر ایلیمنٹ یا احتیاط سے کیلکولیٹ کیے گئے اسپیسر (spacer) کے ذریعے ہوتی ہے۔ نظر آنے والے آئٹمز محض ڈیٹا سیٹ پر پھسلتی ہوئی ایک ونڈو کی طرح ہوتے ہیں۔

اسے ایک پروجیکٹر گیٹ سے گزرتی ہوئی فلم اسٹرپ کی طرح سمجھیں۔ سامعین کو ہموار حرکت نظر آتی ہے، لیکن مشینری صرف اسی فریم کو روشن کرتی ہے جو اس وقت پوزیشن میں ہوتا ہے۔ باقی ریل فیڈ اور ٹیک اپ اسپولز پر موجود ہوتی ہے، روشنی کے راستے میں نہیں۔ ورچوئلائزڈ لسٹیں بھی اسی طرح کام کرتی ہیں۔ ڈیٹا سیٹ ریل ہے۔ ویو پورٹ گیٹ ہے۔

یہ روایتی معنوں میں لیزی لوڈنگ (lazy loading) نہیں ہے۔ لیزی لوڈنگ ڈیٹا حاصل کرنے کو اس وقت تک ملتوی کرتی ہے جب تک صارف اس کے قریب اسکرول نہ کر لے۔ ورچوئلائزیشن یہ فرض کرتی ہے کہ آپ کے پاس ڈیٹا پہلے سے موجود ہے، لیکن آپ اس بارے میں انتخاب کرتے ہیں کہ کون سے حصے حقیقی DOM ایلیمنٹس میں تبدیل کیے جائیں گے۔ یہ دونوں تکنیکیں مل کر کام کر سکتی ہیں، لیکن یہ مختلف مسائل کو حل کرتی ہیں۔

فرق فوری طور پر کیوں محسوس ہوتا ہے

اس کے فوائد چار جگہوں پر نظر آتے ہیں، جو سب ایک ہی بنیادی سکون سے جڑے ہوئے ہیں: آپ اس چیز کے لیے ادائیگی کرنا بند کر دیتے ہیں جسے صارف دیکھ نہیں سکتا۔

تیز ابتدائی لوڈ ٹائم۔ جب براؤزر پیج کھولتا ہے، تو وہ پندرہ ہزار کے بجائے شاید صرف پندرہ لائنیں پینٹ کرتا ہے۔ پہلا بامعنی پینٹ (meaningful paint) جلد آ جاتا ہے۔ ٹائم ٹو انٹرایکٹو (time-to-interactive) کم ہو جاتا ہے کیونکہ جاوا اسکرپٹ انجن نوڈز بنانے اور انہیں دستاویز کے ساتھ جوڑنے میں کم وقت صرف کرتا ہے۔

کم میموری کا استعمال۔ ایک DOM نوڈ ایک مہنگا آبجیکٹ ہے۔ ہر ایک کے ساتھ اسٹائل رولز، لے آؤٹ میٹرکس، اور ایونٹ بائنڈنگز کے حوالے (references) ہوتے ہیں۔ ایکٹیو نوڈ کی تعداد کو چند درجن تک کم کر دیں، تو میموری کا استعمال تیزی سے گر جاتا ہے۔ کم درجے کے آلات یا طویل سیشنز پر، یہ اکیلا ہی ٹیب کو آپریٹنگ سسٹم کے ذریعے بند ہونے سے بچا سکتا ہے۔

ہموار اسکرولنگ کارکردگی۔ ٹری میں کم نوڈز کے ساتھ، براؤزر اسکرول ایونٹس کے دوران لے آؤٹ اور پینٹ مراحل میں کم وقت صرف کرتا ہے۔ کمپوزٹر تھریڈ (compositor thread) چھپے ہوئے مواد کی جیومیٹری کا بار بار حساب لگائے بغیر حرکت کو سنبھال سکتا ہے۔ اس کا نتیجہ ایسی اسکرولنگ ہے جو مانیٹر کے ریفریش ریٹ کے قریب رہتی ہے۔

مستحکم فریم ریٹس۔ چونکہ مین تھریڈ اب لے آؤٹ کے کام میں ڈوب نہیں رہا، اس لیے دوسری سرگرمیوں کے لیے جگہ موجود ہوتی ہے۔ اینیمیشنز ہموار رہتی ہیں۔ نیٹ ورک کے جوابات پر کارروائی کی جا سکتی ہے۔ جب نیا ڈیٹا آتا ہے تو UI جمڈ (freeze) نہیں ہوتا کیونکہ رینڈر پاتھ اب رکاوٹ نہیں رہا۔

امپلیمنٹیشن کو درست طریقے سے کرنا

React کے ایکو سسٹم میں، react-window اور زیادہ بھاری react-virtualized جیسی لائبریریاں اس پیٹرن کے لیے مشینری فراہم کرتی ہیں۔ بنیادی خیال مستقل ہے: آپ ایک آئٹم رینڈرر (item renderer) کی تعریف کرتے ہیں، کل آئٹم کی تعداد بتاتے ہیں، اور لائبریری ونڈونگ میتھ (windowing math) کو سنبھالتی ہے۔ لیکن تفصیلات لوگوں کو الجھا دیتی ہیں۔

سب سے پہلے، کنٹینر (container) کی اونچائی (height) کا متعین ہونا ضروری ہے۔ اگر لسٹ کسی ایسے پیرنٹ (parent) کے اندر ہے جو اپنے بچوں (children) کے مطابق پھیلتا ہے، تو ورچوئلائزیشن (virtualization) یہ حساب نہیں لگا سکے گی کہ کون سی آئٹمز نظر آ رہی ہیں کیونکہ کوئی ویو پورٹ باؤنڈری (viewport boundary) موجود نہیں ہوتی۔ آپ کو لسٹ کو ایک مقررہ اونچائی یا معلوم حدود والے فلیکس کنٹینر (flex container) میں لاک کرنا ہوگا۔

دوسرا، آئٹم کا سائز بہت اہمیت رکھتا ہے۔ فکسڈ ہائٹ (fixed-height) والی قطاریں (rows) سب سے سادہ صورتحال ہیں۔ لائبریری ہر قطار کی اونچائی کو اس کے انڈیکس (index) سے ضرب دیتی ہے اور بالکل جانتی ہے کہ ہر عنصر کو کہاں رکھنا ہے۔ متغیر اونچائی والا مواد (variable-height content)، جیسے کہ تصاویر پر مشتمل چیٹ میسجز یا کمنٹ تھریڈز، لائبریری کو ماؤنٹ (mount) کے بعد پیمائش کرنے اور ضرورت کے مطابق ایڈجسٹ کرنے پر مجبور کرتا ہے۔ پیمائش کا یہ مرحلہ اسکرول کے دوران جھٹکے (scroll jitter) کا باعث بن سکتا ہے اگر یہ بہت دیر سے ہو۔ اگر آپ کا ڈیٹا اجازت دے، تو یکساں اونچائی یا کم از کم اونچائی کو لازمی بنائیں۔ اگر نہیں، تو متغیر اونچائی والے ورچوئلائزر (variable-height virtualizer) کا استعمال کریں اور اضافی پیچیدگی کو قبول کریں۔

تیسرا، اوور اسکیننگ (overscanning) آپ کا دوست ہے۔ اسکرین پر بالکل وہی چیز رینڈر کرنا جو نظر آ رہی ہو، تیز اسکرولنگ کے دوران سفید خالی پٹیاں پیدا کرتا ہے۔ زیادہ تر لائبریریوں میں آپ کو اسکرین کے اوپر اور نیچے کچھ اضافی آئٹمز رینڈر کرنے کی اجازت ہوتی ہے۔ عام طور پر دو یا تین قطاروں کا اوور اسکیننگ DOM کو دوبارہ بھاری کیے بغیر جوڑوں (seams) کو چھپانے کے لیے کافی ہوتا ہے۔

چوتھا، key پروپ (prop) کو نظر انداز نہ کریں۔ ایک ورچوئلائزڈ لسٹ میں، اسکرول کرتے وقت آئٹمز DOM نوڈز کو دوبارہ استعمال کرتی ہیں۔ مستحکم کیز (stable keys) ری ایکٹ (React) کو ری کنسلی ایشن (reconciliation) کے دوران غلط اندازہ لگانے اور رو کمپوننٹس (row components) کے اندر اسٹیٹ (state) کو تباہ کرنے سے روکتی ہیں۔ اگر آپ کی لسٹ کی قطاروں میں ان پٹس (inputs)، ٹوگلز (toggles)، یا پھیلنے والے حصے (expandable sections) شامل ہیں، تو خراب کیز UI اسٹیٹ کو اس طرح خراب کر دیں گی جو آپ کے ڈیٹا لیئر میں بگ (bug) معلوم ہوں گے لیکن حقیقت میں وہ رینڈرنگ کی غلطیاں ہوں گی۔

ایک باریک تر جال براؤزر کا 'فائنڈ ان پیج' (find-in-page) فیچر ہے۔ چونکہ چھپی ہوئی آئٹمز DOM میں موجود نہیں ہوتیں، اس لیے براؤزر کا سرچ باکس انہیں نہیں دیکھ سکے گا۔ اگر آپ کے صارفین بڑی لسٹ کے اندر متن تلاش کرنے کے لیے Ctrl+F پر انحصار کرتے ہیں، تو آپ کو ایک کسٹم سرچ بنانی ہوگی جو دستاویز (document) کے بجائے ڈیٹا سیٹ (dataset) پر کام کرے۔ اسکرین ریڈرز (screen readers) بھی سیاق و سباق (context) کھو سکتے ہیں اگر لسٹ کے سیمنٹکس (semantics) کو احتیاط سے نہ سنبھالا جائے، اس لیے معاون ٹیکنالوجی (assistive technology) کے ساتھ ٹیسٹ کریں اور ڈائنامک لوڈنگ کے لیے لائیو ریجن اعلانات (live region announcements) شامل کرنے پر غور کریں۔

کب آپ کو اسے چھوڑ دینا چاہیے

ورچوئلائزیشن مفت نہیں ہے۔ یہ ڈیپینڈنسی کا وزن (dependency weight)، کوآرڈینیٹ میتھ (coordinate math)، اور کنسٹرینٹ اوور ہیڈ (constraint overhead) کا اضافہ کرتی ہے۔ اگر آپ کی لسٹ پچاس یا سو آئٹمز تک محدود ہے، تو براؤزر اسے بغیر کسی مدد کے سنبھال سکتا ہے۔ پوری لسٹ رینڈر کریں اور آگے بڑھیں۔ یہی بات اس صورت میں بھی لاگو ہوتی ہے اگر آپ کی لسٹ کے آئٹمز انفرادی طور پر انتہائی پیچیدہ ہوں۔ ورچوئلائزیشن آپ کو ہزاروں نوڈز سے بچاتی ہے، لیکن یہ آپ کو ایک ایسے نوڈ سے نہیں بچا سکتی جس میں کوئی بڑا چارٹ یا ویڈیو ایلیمنٹ ہو۔ پہلے آئٹم کے پھیلاؤ (item bloat) کو ٹھیک کریں۔

اس کے علاوہ، جب لسٹ اسکرول نہ ہو رہی ہو تو ورچوئلائزیشن سے گریز کریں۔ اگر آپ 'اگلا' (next) اور 'پچھلا' (previous) بٹنوں کے ساتھ پیجنگ (paginating) کر رہے ہیں اور فی پیج صرف بیس آئٹمز دکھا رہے ہیں، تو ونڈو (window) کرنے کے لیے کچھ نہیں ہے۔ یہ تکنیک صرف اس وقت فائدہ مند ہے جب صارف ایک بڑی مسلسل ترتیب (contiguous sequence) کے ذریعے اسکرول کرنے کی توقع رکھتا ہو۔

اصل حاصلِ کلام

ورچوئلائزیشن لائبریری کا انتخاب کم اور ایک سوچ (mindset) زیادہ ہے۔ یہ آپ کو اس بات کو تسلیم کرنے پر مجبور کرتی ہے کہ DOM ایک محدود وسیلہ (finite resource) ہے، نہ کہ ایک لامتناہی کینوس (infinite canvas)۔ اسے شامل کرنے سے پہلے، Chrome DevTools کھولیں، پرفارمنس پروفائل ریکارڈ کریں، اور تصدیق کریں کہ کیا واقعی لے آؤٹ (layout) یا پینٹ ٹائم (paint time) ہی اصل مسئلہ ہے۔ جب آپ جان لیں کہ DOM ہی رکاوٹ (bottleneck) ہے، تو حدود (constraints) کو اپنا لیں۔ اپنی اونچائیوں کو مقرر کریں، اپنی کیز (keys) پر نظر رکھیں، مناسب حد تک اوور اسکیننگ کریں، اور اپنی رسائی (accessibility) کو ٹیسٹ کریں۔ اگر صحیح طریقے سے کیا جائے، تو ایک ورچوئلائزڈ لسٹ ناقابل استعمال ڈیٹا کی دیوار کو ایسی چیز میں بدل دیتی ہے جو نیٹیو اسکرول ویو (native scroll view) کی طرح ہلکی محسوس ہوتی ہے۔ براؤزر لڑنا بند کر دیتا ہے، آپ کے صارفین انتظار کرنا چھوڑ دیتے ہیں، اور ایپ آخر کار اس تیز انٹرفیس کی طرح کام کرنے لگتی ہے جسے بنانے کا آپ نے ارادہ کیا تھا۔