वह DOM बॉटलनेक जिसके बारे में कोई बात नहीं करता

एक सपोर्ट डैशबोर्ड की कल्पना करें जो दस हजार लॉग एंट्रीज़ (log entries) खींच रहा है। या एक CRM जो एक ही स्क्रॉल करने योग्य टेबल में हर कॉन्टैक्ट को दिखाने की कोशिश कर रहा है। React में, इसे बनाने वाला कोड काफी सुरक्षित दिखता है। आप एक ऐरे (array) पर मैप करते हैं, कुछ JSX रिटर्न करते हैं, और फ्रेमवर्क को अपना काम करने देते हैं। डेवलपमेंट में सौ पंक्तियों के साथ सब कुछ ठीक काम करता है। फिर जब प्रोडक्शन डेटा आता है, तो पेज की गति बहुत धीमी हो जाती है।

ब्राउज़र आलसी नहीं हो रहा है। वह वही कर रहा है जो आपने उससे कहा है, और यही समस्या है। हर पंक्ति एक DOM नोड बन जाती है। हर नोड को स्टाइल किया जाता है, लेआउट किया जाता है, पेंट किया जाता है, और मेमोरी में ट्रैक किया जाता है। जब आप स्क्रॉल करते हैं, तो ब्राउज़र केवल उस हिस्से के लिए नहीं, बल्कि पूरे ट्री (tree) के लिए पोजीशन की पुनर्गणना (recalculate) करता है जिसे आप देख रहे हैं। इवेंट लिसनर्स (Event listeners) जमा होने लगते हैं। मेमोरी बढ़ जाती है। अंततः मेन थ्रेड (main thread) इतना चोक हो जाता है कि इंटरफ़ेस क्लिक, कीस्ट्रोक या स्क्रॉल पर भी प्रतिक्रिया देना बंद कर देता है। तकनीकी रूप से एप्लिकेशन क्रैश नहीं हुई है, लेकिन इसके सामने बैठे उपयोगकर्ता के लिए, अनुभव पूरी तरह से खराब हो चुका है।

ऐसा इसलिए होता है क्योंकि ब्राउज़र एक ही समय में हर एक एलिमेंट को एक्टिव मेमोरी में रखने की कोशिश करता है। React आपके UI के वर्चुअल विवरण (virtual descriptions) बनाने में कुशल हो सकता है, लेकिन एक बार जब वे विवरण डॉक्यूमेंट में वास्तविक नोड्स बन जाते हैं, तो उनकी लागत हाथ से लिखे गए HTML के समान ही होती है। फ्रेमवर्क के भीतर ही इससे बचने का कोई रास्ता नहीं है। आपको इस बात में संरचनात्मक बदलाव (structural change) करने की आवश्यकता है कि आप लिस्ट को DOM में कैसे फीड करते हैं।

वर्चुअलाइजेशन (Virtualization) का वास्तव में क्या अर्थ है

वर्चुअलाइजेशन वही संरचनात्मक बदलाव है। React से पूरे ऐरे को रेंडर करने के लिए कहने के बजाय, आप केवल उन आइटम्स को रेंडर करते हैं जो व्यूपोर्ट (viewport) के अंदर फिट हो सकते हैं, साथ ही ऊपर और नीचे एक छोटा बफर (buffer) भी। जैसे-जैसे उपयोगकर्ता स्क्रॉल करता है, एप्लिकेशन उन नोड्स को हटा देता है जो दृश्य से बाहर चले जाते हैं और विपरीत किनारे से आने वाले नए नोड्स को इंस्टेंटिएट (instantiate) करता है। उपयोगकर्ता के लिए, यह अभी भी एक निरंतर सूची की तरह महसूस होता है क्योंकि कुल स्क्रॉल करने योग्य ऊंचाई सुरक्षित रहती है, जो आमतौर पर एक एकल लंबे कंटेनर एलिमेंट या सावधानीपूर्वक गणना किए गए स्पेसर (spacer) के माध्यम से होती है। दिखाई देने वाले आइटम बस डेटासेट पर फिसलती हुई एक विंडो की तरह होते हैं।

इसे प्रोजेक्टर गेट के माध्यम से चलने वाली एक फिल्मस्ट्रिप की तरह समझें। दर्शक सुचारू गति देखते हैं, लेकिन मशीनरी केवल उसी फ्रेम को रोशन करती है जो वर्तमान में स्थिति में है। बाकी रील फीड और टेक-अप स्पूल पर मौजूद होती है, लाइट पाथ में नहीं। वर्चुअलाइज्ड लिस्ट इसी तरह काम करती हैं। डेटासेट रील है। व्यूपोर्ट गेट है।

यह पारंपरिक अर्थों में 'लेज़ी लोडिंग' (lazy loading) नहीं है। लेज़ी लोडिंग डेटा को तब तक लाने में देरी करती है जब तक कि उपयोगकर्ता उसके पास स्क्रॉल न कर ले। वर्चुअलाइजेशन यह मानकर चलता है कि आपके पास पहले से ही डेटा है, लेकिन आप इस बारे में चयनात्मक (selective) हैं कि कौन से हिस्से वास्तविक DOM एलिमेंट्स में प्रमोट किए जाएंगे। दोनों तकनीकें एक साथ काम कर सकती हैं, लेकिन वे अलग-अलग समस्याओं का समाधान करती हैं।

यह अंतर तुरंत क्यों महसूस होता है

इसके लाभ चार जगहों पर दिखाई देते हैं, जो सभी एक ही अंतर्निहित राहत से जुड़े हैं: आप उस चीज़ के लिए भुगतान करना बंद कर देते हैं जिसे उपयोगकर्ता देख नहीं सकता।

तेज़ शुरुआती लोड समय। जब ब्राउज़र पेज खोलता है, तो वह पंद्रह हज़ार के बजाय शायद पंद्रह पंक्तियों को पेंट करता है। पहला सार्थक पेंट (meaningful paint) जल्दी आता है। टाइम-टू-इंटरएक्टिव (time-to-interactive) कम हो जाता है क्योंकि जावास्क्रिप्ट इंजन नोड्स बनाने और उन्हें डॉक्यूमेंट से जोड़ने में कम समय बिताता है।

कम मेमोरी उपयोग। एक DOM नोड एक महंगा ऑब्जेक्ट है। प्रत्येक में स्टाइल नियमों, लेआउट मेट्रिक्स और इवेंट बाइंडिंग के संदर्भ (references) होते हैं। एक्टिव नोड की संख्या को कुछ दर्जन तक कम कर दें, और मेमोरी फुटप्रिंट काफी कम हो जाता है। लो-एंड डिवाइस या लंबे सत्रों (sessions) में, अकेले यह ऑपरेटिंग सिस्टम द्वारा टैब को बंद किए जाने से रोक सकता है।

सुचारू स्क्रॉलिंग प्रदर्शन। ट्री में कम नोड्स होने के साथ, ब्राउज़र स्क्रॉल इवेंट के दौरान लेआउट और पेंट चरणों में कम समय बिताता है। कंपोजिटर थ्रेड (compositor thread) छिपे हुए कंटेंट की ज्यामिति (geometry) की लगातार पुनर्गणना किए बिना मूवमेंट को संभाल सकता है। परिणाम ऐसी स्क्रॉलिंग है जो मॉनिटर के रिफ्रेश रेट के करीब रहती है।

स्थिर फ्रेम रेट। क्योंकि मेन थ्रेड अब लेआउट के काम में नहीं डूब रहा है, इसलिए अन्य गतिविधियों के लिए गुंजाइश (headroom) उपलब्ध होती है। एनिमेशन सुचारू रहते हैं। नेटवर्क रिस्पॉन्स को प्रोसेस किया जा सकता है। जब नया डेटा आता है तो UI फ्रीज़ नहीं होता क्योंकि रेंडर पाथ अब बॉटलनेक नहीं रह जाता है।

इम्प्लीमेंटेशन को सही तरीके से करना

React इकोसिस्टम में, react-window और भारी react-virtualized जैसी लाइब्रेरीज़ इस पैटर्न के लिए मशीनरी प्रदान करती हैं। मूल विचार सुसंगत है: आप एक आइटम रेंडरर को परिभाषित करते हैं, कुल आइटम काउंट पास करते हैं, और लाइब्रेरी विंडोइंग मैथ (windowing math) को मैनेज करती है। लेकिन विवरण लोगों को उलझा देते हैं।

सबसे पहले, कंटेनर की एक निश्चित ऊंचाई (defined height) होनी चाहिए। यदि लिस्ट किसी ऐसे पैरेंट के अंदर है जो अपने बच्चों के अनुसार फैलता है, तो वर्चुअलाइजेशन यह गणना नहीं कर पाएगा कि कौन सी आइटम्स दिखाई दे रही हैं क्योंकि कोई व्यूपोर्ट बाउंड्री (viewport boundary) नहीं होती। आपको लिस्ट को एक निश्चित ऊंचाई या ज्ञात बाधाओं (constraints) वाले फ्लेक्स कंटेनर में लॉक करना होगा।

दूसरा, आइटम का आकार (item sizing) बहुत मायने रखता है। फिक्स्ड-हाइट वाली पंक्तियाँ (rows) सबसे सरल मामला हैं। लाइब्रेरी रो की ऊंचाई को इंडेक्स से गुणा करती है और जानती है कि प्रत्येक एलिमेंट को ठीक कहाँ रखना है। वेरिएबल-हाइट कंटेंट, जैसे कि एम्बेडेड इमेज वाले चैट मैसेज या कमेंट थ्रेड्स, लाइब्रेरी को माउंट होने के बाद मापने और तुरंत एडजस्ट करने के लिए मजबूर करते हैं। यदि यह माप प्रक्रिया बहुत देर से होती है, तो इससे स्क्रॉल जिटर (scroll jitter) हो सकता है। यदि आपका डेटा अनुमति देता है, तो एकसमान ऊंचाई या न्यूनतम ऊंचाई लागू करें। यदि नहीं, तो वेरिएबल-हाइट वर्चुअलाइज़र का उपयोग करें और अतिरिक्त जटिलता को स्वीकार करें।

तीसरा, ओवरस्कैनिंग (overscanning) आपकी मित्र है। स्क्रीन पर जो कुछ भी फिट बैठता है, केवल उसे रेंडर करने से जब उपयोगकर्ता तेज़ी से स्क्रॉल करता है, तो खाली सफेद पट्टियाँ दिखाई देने लगती हैं। अधिकांश लाइब्रेरी आपको फोल्ड के ऊपर और नीचे कुछ अतिरिक्त आइटम्स रेंडर करने की अनुमति देती हैं। DOM को फिर से फुलाए बिना सीम (seams) को छिपाने के लिए आमतौर पर दो या तीन पंक्तियों का ओवरस्कैन पर्याप्त होता है।

चौथा, key प्रॉप को नज़रअंदाज़ न करें। एक वर्चुअलाइज्ड लिस्ट में, स्क्रॉल करते समय आइटम्स DOM नोड्स का पुन: उपयोग करते हैं। स्टेबल कीज़ (stable keys) रिकॉन्सिलिएशन (reconciliation) के दौरान React को गलत अनुमान लगाने और रो कंपोनेंट्स के अंदर स्टेट को नष्ट करने से रोकती हैं। यदि आपकी लिस्ट की पंक्तियों में इनपुट्स, टॉगल या विस्तार योग्य (expandable) सेक्शन हैं, तो खराब कीज़ UI स्टेट को इस तरह से खराब कर सकती हैं जो आपके डेटा लेयर में बग की तरह लग सकते हैं, लेकिन वास्तव में वे रेंडरिंग की गलतियाँ होती हैं।

एक सूक्ष्म जाल ब्राउज़र का 'find-in-page' है। क्योंकि छिपे हुए आइटम्स DOM में मौजूद नहीं होते हैं, इसलिए ब्राउज़र का सर्च बॉक्स उन्हें नहीं देख पाएगा। यदि आपके उपयोगकर्ता बड़ी लिस्ट के अंदर टेक्स्ट खोजने के लिए Ctrl+F पर निर्भर हैं, तो आपको एक कस्टम सर्च बनाना होगा जो डॉक्यूमेंट के बजाय डेटासेट पर काम करे। यदि लिस्ट के सिमेंटिक्स (semantics) को सावधानी से हैंडल नहीं किया गया है, तो स्क्रीन रीडर्स भी संदर्भ (context) खो सकते हैं, इसलिए सहायक तकनीक (assistive technology) के साथ परीक्षण करें और डायनेमिक लोडिंग के लिए लाइव रीजन घोषणाओं (live region announcements) को जोड़ने पर विचार करें।

आपको इसे कब छोड़ देना चाहिए

वर्चुअलाइजेशन मुफ्त नहीं है। यह डिपेंडेंसी वेट, कोऑर्डिनेट मैथ और कंस्ट्रेंट ओवरहेड जोड़ता है। यदि आपकी लिस्ट पचास या सौ आइटम्स तक ही सीमित है, तो ब्राउज़र इसे बिना किसी मदद के संभाल सकता है। पूरी चीज़ को रेंडर करें और आगे बढ़ें। यही बात तब भी लागू होती है जब आपकी लिस्ट के आइटम्स व्यक्तिगत रूप से अत्यधिक जटिल हों। वर्चुअलाइजेशन आपको हज़ारों नोड्स से बचाता है, लेकिन यह आपको एक ऐसे नोड से नहीं बचा सकता जिसमें एक विशाल चार्ट या वीडियो एलिमेंट हो। पहले आइटम के भारीपन (item bloat) को ठीक करें।

साथ ही, जब लिस्ट स्क्रॉल न हो, तब वर्चुअलाइजेशन से बचें। यदि आप 'next' और 'previous' बटन के साथ पेजिंग कर रहे हैं और प्रति पेज केवल बीस आइटम दिखा रहे हैं, तो विंडो करने के लिए कुछ भी नहीं है। यह तकनीक तभी सार्थक होती है जब उपयोगकर्ता एक बड़े निरंतर अनुक्रम (contiguous sequence) के माध्यम से स्क्रॉल करने की अपेक्षा करता है।

असली निष्कर्ष

वर्चुअलाइजेशन कम और लाइब्रेरी का चुनाव कम, बल्कि एक मानसिकता (mindset) अधिक है। यह आपको यह स्वीकार करने के लिए मजबूर करता है कि DOM एक सीमित संसाधन है, न कि एक अनंत कैनवास। इसे जोड़ने से पहले, Chrome DevTools खोलें, एक परफॉरमेंस प्रोफाइल रिकॉर्ड करें, और पुष्टि करें कि क्या वास्तव में लेआउट या पेंट टाइम ही असली अपराधी है। एक बार जब आप जान लें कि DOM ही बाधा (bottleneck) है, तो बाधाओं (constraints) के प्रति प्रतिबद्ध हो जाएं। अपनी ऊंचाइयों को लॉक करें, अपनी कीज़ पर नज़र रखें, समझदारी से ओवरस्कैन करें, और अपनी एक्सेसिबिलिटी का परीक्षण करें। सही ढंग से किए जाने पर, एक वर्चुअलाइज्ड लिस्ट एक अनुपयोगी डेटा वॉल को ऐसी चीज़ में बदल देती है जो एक नेटिव स्क्रॉल व्यू की तरह हल्की महसूस होती है। ब्राउज़र लड़ना बंद कर देता है, आपके उपयोगकर्ता प्रतीक्षा करना बंद कर देते हैं, और ऐप अंततः वैसा ही व्यवहार करता है जैसा कि आपने एक तेज़ इंटरफ़ेस बनाने का इरादा किया था।