ज्या DOM बॉटलनेकबद्दल कोणीही बोलत नाही

एका सपोर्ट डॅशबोर्डची कल्पना करा जो दहा हजार लॉग एन्ट्रीज खेचत आहे. किंवा एक CRM जो एकाच स्क्रोल करण्यायोग्य टेबलमध्ये प्रत्येक कॉन्टॅक्ट दाखवण्याचा प्रयत्न करत आहे. React मध्ये, हे तयार करण्यासाठीचा कोड पुरेसा निरुपद्रवी दिसतो. तुम्ही एका array वर map करता, काही JSX रिटर्न करता आणि फ्रेमवर्कला त्याचे काम करू देता. डेव्हलपमेंटमध्ये शंभर रो (rows) असताना सर्व काही व्यवस्थित चालते. पण जेव्हा प्रोडक्शन डेटा येतो, तेव्हा पेज अत्यंत संथ होते.

ब्राउझर आळशी नाहीये. तुम्ही त्याला जे सांगितले आहे तेच तो अगदी तंतोतंत करत आहे आणि तीच समस्या आहे. प्रत्येक रो (row) एक DOM node बनतो. प्रत्येक नोडला स्टाईल दिली जाते, लेआउट केला जातो, पेंट केला जातो आणि मेमरीमध्ये ट्रॅक केला जातो. जेव्हा तुम्ही स्क्रोल करता, तेव्हा ब्राउझर फक्त तुम्ही पाहत असलेल्या भागासाठी नाही, तर संपूर्ण ट्रीसाठी पोझिशन्स पुन्हा कॅल्क्युलेट करतो. इव्हेंट लिसनर्स (Event listeners) साचत जातात. मेमरी वाढते. अखेरीस मेन थ्रेड (main thread) इतका अडकतो की इंटरफेस क्लिक्स, कीस्ट्रोक्स किंवा अगदी स्क्रोललाही प्रतिसाद देणे थांबवतो. तांत्रिकदृष्ट्या ॲप्लिकेशन क्रॅश झालेले नसते, परंतु समोर बसलेल्या वापरकर्त्यासाठी अनुभव मात्र पूर्णपणे बिघडलेला असतो.

हे घडते कारण ब्राउझर प्रत्येक घटक एकाच वेळी ॲक्टिव्ह मेमरीमध्ये ठेवण्याचा प्रयत्न करतो. React तुमच्या UI चे व्हर्च्युअल वर्णन (virtual descriptions) तयार करण्यात कार्यक्षम असू शकते, परंतु एकदा का ही वर्णने डॉक्युमेंटमधील रिअल नोड्स बनली की, त्यांचा खर्च हाताने लिहिलेल्या HTML इतकाच असतो. फ्रेमवर्कमध्ये स्वतःहून यातून बाहेर पडण्याचा कोणताही मार्ग नाही. तुम्हाला लिस्ट DOM ला कशा प्रकारे फीड करता, यामध्ये स्ट्रक्चरल बदल करण्याची गरज आहे.

व्हर्च्युअलायझेशनचा (Virtualization) नेमका अर्थ काय आहे

व्हर्च्युअलायझेशन हा तोच स्ट्रक्चरल बदल आहे. React ला संपूर्ण array रेंडर करायला सांगण्याऐवजी, तुम्ही फक्त तेच आयटम्स रेंडर करता जे व्ह्यूपोर्टमध्ये (viewport) बसू शकतात, सोबत वर आणि खाली एक छोटा बफर (buffer) देखील. वापरकर्ता स्क्रोल करत असताना, ॲप्लिकेशन व्ह्यूमधून बाहेर जाणाऱ्या नोड्सना काढून टाकते आणि विरुद्ध बाजूने येणाऱ्या नवीन नोड्सना इन्स्टँशिएट (instantiate) करते. वापरकर्त्याला ते अजूनही एक सलग लिस्ट असल्यासारखे वाटते कारण एकूण स्क्रोल करण्यायोग्य उंची (scrollable height) जपली जाते, जी सहसा एका मोठ्या कंटेनर एलिमेंटद्वारे किंवा काळजीपूर्वक कॅल्क्युलेट केलेल्या स्पेसरद्वारे (spacer) राखली जाते. दिसणारे आयटम्स म्हणजे केवळ डेटासेटवर सरकणारी एक खिडकी आहे.

हे प्रोजेक्टर गेटमधून जाणार्‍या फिल्मस्ट्रिपसारखे समजा. प्रेक्षक स्मूथ मोशन पाहतात, परंतु यंत्रणा फक्त सध्याच्या पोझिशनमधील फ्रेमवर प्रकाश टाकते. रीलचा उर्वरित भाग फीड आणि टेक-अप स्पूल्सवर असतो, लाईट पाथमध्ये नाही. व्हर्च्युअलाइज्ड लिस्ट्स देखील याच पद्धतीने काम करतात. डेटासेट म्हणजे रील आहे आणि व्ह्यूपोर्ट म्हणजे गेट आहे.

हे पारंपारिक अर्थाने 'लेझी लोडिंग' (lazy loading) नाही. लेझी लोडिंगमध्ये वापरकर्ता जवळ येईपर्यंत डेटा फेच करणे पुढे ढकलले जाते. व्हर्च्युअलायझेशनमध्ये असे मानले जाते की तुमच्याकडे डेटा आधीच आहे, परंतु तुम्ही कोणत्या स्लाईसना रिअल DOM एलिमेंट्समध्ये रूपांतरित करायचे याबद्दल निवडक असता. या दोन्ही तंत्रांचा एकत्र वापर केला जाऊ शकतो, परंतु ते वेगवेगळ्या समस्या सोडवतात.

हा फरक लगेच का जाणवतो

याचे फायदे चार ठिकाणी दिसून येतात, जे सर्व एकाच मूळ सुटकेशी जोडलेले आहेत: तुम्ही वापरकर्त्याला जे दिसत नाही त्यासाठी खर्च करणे थांबवता.

वेगवान सुरुवातीचा लोड वेळ (Initial load times). जेव्हा ब्राउझर पेज उघडतो, तेव्हा तो पंधरा हजार ऐवजी कदाचित फक्त पंधरा रो (rows) पेंट करतो. 'फर्स्ट मिनिंगफुल पेंट' (first meaningful paint) लवकर येते. 'टाइम-टू-इंटरअॅक्टिव्ह' (time-to-interactive) कमी होतो कारण JavaScript इंजिन नोड्स तयार करण्यात आणि ते डॉक्युमेंटला जोडण्यात कमी वेळ घालवते.

कमी मेमरी वापर. DOM नोड ही एक महागडी ऑब्जेक्ट आहे. प्रत्येक नोडमध्ये स्टाईल रूल्स, लेआउट मेट्रिक्स आणि इव्हेंट बाइंडिंगचे संदर्भ असतात. ॲक्टिव्ह नोडची संख्या काही डझनपर्यंत कमी केल्यास, मेमरीचा वापर लक्षणीयरीत्या कमी होतो. कमी क्षमतेच्या उपकरणांवर किंवा दीर्घ सत्रांमध्ये, यामुळेच ऑपरेटिंग सिस्टमद्वारे टॅब बंद होण्यापासून वाचवता येऊ शकते.

स्मूथ स्क्रोलिंग परफॉर्मन्स. ट्रीमध्ये कमी नोड्स असल्यामुळे, स्क्रोल इव्हेंट्स दरम्यान ब्राउझर लेआउट आणि पेंट फेजमध्ये कमी वेळ घालवतो. कंपोजिटर थ्रेड (compositor thread) लपलेल्या कंटेंटची भूमिती (geometry) सतत पुन्हा कॅल्क्युलेट न करता हालचाली हाताळू शकतो. परिणामी, स्क्रोलिंग मॉनिटरच्या रिफ्रेश रेटच्या जवळ राहते.

स्थिर फ्रेम रेट्स. मेन थ्रेड आता लेआउटच्या कामात अडकलेला नसल्यामुळे, इतर क्रियाकलापांसाठी जागा उपलब्ध असते. ॲनिमेशन्स स्मूथ राहतात. नेटवर्क रिस्पॉन्स प्रोसेस केले जाऊ शकतात. नवीन डेटा आल्यावर UI फ्रीझ होत नाही कारण रेंडर पाथ आता बॉटलनेक राहत नाही.

अंमलबजावणी (Implementation) योग्यरित्या करणे

React इकोसिस्टममध्ये, react-window आणि अधिक जड react-virtualized सारख्या लायब्ररी या पॅटर्नसाठी आवश्यक यंत्रणा प्रदान करतात. मूळ कल्पना सुसंगत आहे: तुम्ही एक आयटम रेंडरर (item renderer) परिभाषित करता, एकूण आयटमची संख्या पास करता आणि लायब्ररी विंडोइंग मॅथ (windowing math) व्यवस्थापित करते. परंतु बारकावे लोकांना गोंधळात टाकू शकतात.

पहिले म्हणजे, कंटेनरला निश्चित उंचीची आवश्यकता असते. जर लिस्ट अशा पॅरेंट (parent) घटकामध्ये असेल जो त्याच्या चाईल्ड (children) घटकांनुसार विस्तारतो, तर व्हर्च्युअलायझेशनला कोणते घटक दृश्यमान आहेत हे मोजता येत नाही, कारण तिथे व्ह्यूपोर्टची (viewport) कोणतीही सीमा नसते. तुम्हाला लिस्ट एका निश्चित उंचीमध्ये किंवा ज्ञात मर्यादा असलेल्या फ्लेक्स कंटेनरमध्ये (flex container) लॉक करावी लागेल.

दुसरे म्हणजे, आयटमचा आकार (item sizing) अत्यंत महत्त्वाचा आहे. निश्चित उंचीच्या रो (rows) हे सर्वात सोपे उदाहरण आहे. लायब्ररी रोची उंची इंडेक्सने (index) गुणते आणि प्रत्येक घटक नेमका कुठे ठेवायचा हे तिला अचूक समजते. चॅट मेसेजेस (ज्यात इमेज समाविष्ट आहेत) किंवा कमेंट थ्रेड्स सारख्या बदलत्या उंचीच्या कंटेंटमुळे, लायब्ररीला माउंट (mount) झाल्यानंतर मोजमाप करावे लागते आणि त्यानुसार बदल करावे लागतात. जर हे मोजमाप उशिरा झाले, तर स्क्रोलिंगमध्ये अडथळा (scroll jitter) येऊ शकतो. जर तुमच्या डेटाची परवानगी असेल, तर एकसमान उंची किंवा किमान उंची निश्चित करा. नसेल तर, व्हेरिएबल-हाइट व्हर्च्युअलायझर (variable-height virtualizer) वापरा आणि त्यातील अतिरिक्त गुंतागुंत स्वीकारा.

तिसरे म्हणजे, ओव्हरस्कॅनिंग (overscanning) तुमचा मित्र आहे. स्क्रीनवर जेवढे बसते तेवढेच रेंडर केल्यास, वापरकर्ता वेगाने स्क्रोल करताना पांढऱ्या रंगाच्या रिकाम्या पट्ट्या दिसू शकतात. बहुतेक लायब्ररी तुम्हाला स्क्रीनच्या वर आणि खाली काही अतिरिक्त आयटम्स रेंडर करण्याची परवानगी देतात. DOM पुन्हा फुगवल्याशिवाय (bloating) सांधे (seams) लपवण्यासाठी साधारणपणे दोन किंवा तीन ओव्हर्सकॅन रो पुरेसे असतात.

चौथे म्हणजे, key प्रॉपकडे दुर्लक्ष करू नका. व्हर्च्युअलाइज्ड लिस्टमध्ये, स्क्रोल करताना आयटम्स DOM नोड्सचा पुनर्वापर करतात. स्टेबल कीज (stable keys) मुळे रिकॉन्सिलिएशन (reconciliation) दरम्यान React चुकीचा अंदाज लावत नाही आणि रो कंपोनंट्समधील स्टेट (state) नष्ट होत नाही. जर तुमच्या लिस्ट रोमध्ये इनपुट्स, टॉगल्स किंवा विस्तारण्यायोग्य (expandable) विभाग असतील, तर चुकीच्या कीजमुळे UI स्टेट खराब होऊ शकते. हे तुमच्या डेटा लेयरमधील बग्ससारखे वाटू शकतात, परंतु प्रत्यक्षात ते रेंडरिंगमधील चुका असतात.

एक सूक्ष्म अडथळा म्हणजे ब्राउझरमधील 'find-in-page' (शोध). लपवलेले आयटम्स DOM मध्ये नसल्यामुळे, ब्राउझरच्या सर्च बॉक्सला ते दिसणार नाहीत. जर तुमचे वापरकर्ते मोठ्या लिस्टमध्ये मजकूर शोधण्यासाठी Ctrl+F वर अवलंबून असतील, तर तुम्हाला डेटासेटवर आधारित कस्टम सर्च (custom search) तयार करावा लागेल, डॉक्युमेंटवर नाही. जर लिस्टचे सिमेंटिक्स (semantics) काळजीपूर्वक हाताळले नाहीत, तर स्क्रीन रीडर्स देखील संदर्भ गमावू शकतात, त्यामुळे असिस्टिव्ह टेक्नॉलॉजीसह (assistive technology) चाचणी करा आणि डायनॅमिक लोडिंगसाठी 'live region announcements' जोडण्याचा विचार करा.

तुम्हाला हे कधी टाळले पाहिजे

व्हर्च्युअलायझेशन मोफत नसते. यामुळे डिपेंडन्सी वेट (dependency weight), कोऑर्डिनेट मॅथ (coordinate math) आणि कन्स्ट्रेंट ओव्हरहेड (constraint overhead) वाढतो. जर तुमची लिस्ट पन्नास किंवा शंभर आयटम्सपर्यंत मर्यादित असेल, तर ब्राउझर ते कोणत्याही मदतीशिवाय हाताळू शकतो. संपूर्ण लिस्ट रेंडर करा आणि पुढे जा. जर तुमचे लिस्ट आयटम्स वैयक्तिकरित्या अत्यंत गुंतागुंतीचे असतील, तर देखील हेच लागू होते. व्हर्च्युअलायझेशन तुम्हाला हजारो नोड्सपासून वाचवते, परंतु एका नोडमध्ये असलेला मोठा चार्ट किंवा व्हिडिओ एलिमेंट तुम्हाला वाचवू शकत नाही. आधी आयटममधील अनावश्यक वाढ (bloat) कमी करा.

तसेच, जेव्हा लिस्ट स्क्रोल होत नसेल तेव्हा व्हर्च्युअलायझेशन टाळा. जर तुम्ही 'next' आणि 'previous' बटणांचा वापर करून पेजिनेशन (pagination) करत असाल आणि प्रति पृष्ठ फक्त वीस आयटम्स दाखवत असाल, तर तिथे 'windowing' करण्याची गरज नाही. जेव्हा वापरकर्त्याला मोठ्या सलग क्रमाने (contiguous sequence) स्क्रोल करण्याची अपेक्षा असते, तेव्हाच या तंत्राचा खरा फायदा होतो.

मुख्य निष्कर्ष

व्हर्च्युअलायझेशन हे केवळ लायब्ररी निवडण्यापेक्षा एक विचारसरणी (mindset) आहे. हे तुम्हाला हे मान्य करण्यास भाग पाडते की DOM हा एक मर्यादित स्त्रोत (finite resource) आहे, अनंत कॅनव्हास (infinite canvas) नाही. ते वापरण्यापूर्वी, Chrome DevTools उघडा, परफॉर्मन्स प्रोफाइल (performance profile) रेकॉर्ड करा आणि लेआउट किंवा पेंट टाइम (paint time) खरोखरच अडथळा आहे याची खात्री करा. एकदा तुम्हाला समजले की DOM हा अडथळा (bottleneck) आहे, तेव्हा मर्यादांचे पालन करा. तुमची उंची निश्चित करा, कीजवर (keys) लक्ष ठेवा, मर्यादित ओव्हरस्कॅनिंग करा आणि तुमची ॲक्सेसिबिलिटी (accessibility) तपासा. योग्यरित्या केल्यास, व्हर्च्युअलाइज्ड लिस्ट एका वापरण्याअयोग्य डेटा वॉलचे रूपांतर अशा गोष्टीत करते जी नेटिव्ह स्क्रोल व्ह्यूसारखी (native scroll view) हलकी वाटते. ब्राउझरचा संघर्ष थांबतो, तुमचे वापरकर्ते थांबणे थांबवतात आणि ॲप अखेर तुम्ही बनवलेल्या वेगवान इंटरफेसप्रमाणे काम करते.