फीड में बीस आइटम एकदम सही लगते हैं। स्क्रॉलिंग मक्खन की तरह स्मूथ होती है। आपका क्लाइंट खुश है। फिर आप इसे प्रोडक्शन में भेजते हैं, डेटा आता है, और अचानक आप दो हज़ार पंक्तियों (rows) को घूर रहे होते हैं। UI अटकने लगता है। मेमोरी इतनी बढ़ जाती है कि OS ऐप को बंद कर देता है। हताशा में, कुछ डेवलपर्स सब कुछ एक ScrollView में लपेट देते हैं और काम खत्म मान लेते हैं। वह निर्णय आमतौर पर एक बग सुलझाने के बदले तीन नए बग पैदा कर देता है।
लिस्ट्स परफॉरमेंस की वह बाधा (bottleneck) हैं जो यह तय करती हैं कि उपयोगकर्ता आपके React Native ऐप को कैसे देखते हैं। अगर आप इन्हें सही तरीके से इस्तेमाल करते हैं, तो ऐप नेटिव जैसा महसूस होता है। अगर आप इन्हें गलत तरीके से इस्तेमाल करते हैं, तो सबसे खूबसूरत स्क्रीन भी बोझिल हो जाती है। इसका मूल कारण आमतौर पर उस कंपोनेंट और उस काम के बीच का तालमेल न होना है जो आप JavaScript और UI थ्रेड्स से करने के लिए कह रहे हैं। React Native दो ट्रैक पर चलता है। आपका लॉजिक JS thread पर रहता है जबकि पेंटिंग (painting) native UI thread पर होती है। जब आप किसी विशाल लिस्ट को गलत तरीके से रेंडर करते हैं, तो दोनों थ्रेड्स लेआउट कैलकुलेशन, री-रेंडर और मेमोरी एलोकेशन में डूबने लगते हैं। इसका परिणाम होता है ड्रॉप हुए फ्रेम्स, ब्लैंक फ्लैश और अंततः ऐप का क्रैश होना।
सही टूल चुनें
लिस्ट कंपोनेंट का चुनाव एक सोची-समझी आर्किटेक्चरल पसंद होनी चाहिए, न कि कोई सहज प्रतिक्रिया (reflex)।
ScrollView सबसे सरल विकल्प है। यह आपके द्वारा दिए गए हर चाइल्ड को लेता है, तुरंत हर एक को मेमोरी में माउंट करता है, और पूरे ढेर को नेटिव स्क्रॉल इंजन को सौंप देता है। यह बिल्कुल वही है जो आपको सेटिंग्स स्क्रीन, लॉगिन फॉर्म, या दस सेक्शन वाले किसी स्टैटिक प्रोडक्ट डिटेल पेज जैसे छोटे और निश्चित कंटेंट के लिए चाहिए। यह प्रेडिक्टेबल है और इसे स्टाइल करना आसान है। पेच यह है कि इसमें कोई वर्चुअलाइजेशन (virtualization) नहीं होता है। यदि आप इसमें दो हज़ार आइटम देते हैं, तो यह आज्ञाकारी रूप से दो हज़ार नेटिव व्यूज़ बना देगा। बड़े या डायनामिक डेटा सेट के लिए ScrollView का उपयोग न करें। इसे एक फ्रेम किए हुए पोस्टर की तरह समझें, लाइब्रेरी की शेल्फ की तरह नहीं।
FlatList लंबे और एक समान फीड्स के लिए काम करने वाला मुख्य टूल (workhorse) है। यह कंटेंट को वर्चुअलाइज करता है, जिसका अर्थ है कि यह केवल उन्हीं पंक्तियों (rows) को माउंट करता है जो वर्तमान में दिखाई दे रही हैं या व्यूपोर्ट के करीब हैं। जैसे-जैसे उपयोगकर्ता स्क्रॉल करता है, FlatList उन सेल्स को अनमाउंट कर देता है जो स्क्रीन से बाहर चले जाते हैं और उन्हें आने वाले डेटा के लिए रीसायकल कर देता है। इससे आपकी एरे (array) कितनी भी बड़ी क्यों न हो जाए, मेमोरी स्थिर रहती है। यदि आप एक सोशल टाइमलाइन, नोटिफिकेशन सेंटर, या समान कार्ड्स का कोई निरंतर स्क्रॉल होने वाला कलेक्शन बना रहे हैं, तो FlatList डिफ़ॉल्ट रूप से सही विकल्प है।
SectionList संगठन की भावना के साथ FlatList है। इसका उपयोग तब करें जब आपका डेटा ग्रुप किए हुए बकेट में आता है, जैसे कि वर्णानुक्रम (alphabetically) में व्यवस्थित एड्रेस बुक, तारीख के अनुसार विभाजित वर्कआउट लॉग, या महीने के अनुसार व्यवस्थित इनवॉइस लिस्ट। यह स्टिकी सेक्शन हेडर्स रेंडर करता है और आपके लिए ग्रुपिंग लॉजिक को संभालता है। यह अंदरूनी रूप से FlatList के समान ही वर्चुअलाइजेशन इंजन का उपयोग करता है, इसलिए आपको शीर्षक वाले विभाजनों (titled partitions) की अतिरिक्त संरचना के साथ समान मेमोरी लाभ मिलते हैं।
FlashList तब काम आता है जब आपको डिवाइस से एक-एक फ्रेम निचोड़ना हो। RecyclerListView इकोसिस्टम पर निर्मित, यह FlatList की तुलना में अधिक आक्रामक रूप से व्यूज़ को रीसायकल करता है और मिड-रेंज हार्डवेयर पर भी साठ (sixty) फ्रेम्स प्रति सेकंड बनाए रखने का लक्ष्य रखता है। यदि आप एक हाई-वॉल्यूम चैट इंटरफ़ेस, तेज़ स्क्रॉल वेलोसिटी वाला प्रोडक्ट कैटलॉग, या कोई ऐसा स्क्रीन बना रहे हैं जहाँ स्मूथनेस एक प्रतिस्पर्धी लाभ (competitive advantage) है, तो FlashList अतिरिक्त डिपेंडेंसी के लायक है। यह हर स्क्रीन के लिए आवश्यक नहीं है, लेकिन उन फीड्स के लिए जो मुख्य अनुभव (core experience) को परिभाषित करते हैं, परफॉरमेंस का अंतर स्पष्ट रूप से दिखाई देता है।
सामान्य परफॉरमेंस किलर्स
जब कोई लिस्ट धीमी होने लगती है, तो तीन सामान्य संदिग्ध होते हैं।
एक साथ बहुत अधिक React ट्रीज़ को माउंट करना सबसे नाटकीय विफलता है। जब हर पंक्ति एक जटिल कंपोनेंट ट्री होती है, तो शुरुआती रेंडर JS thread को इतना ब्लॉक कर सकता है कि एक खाली सफेद स्क्रीन या एक बदसूरत देरी से दिखने वाला फर्स्ट पेंट (first paint) दिखाई दे। उपयोगकर्ता ऐप खोलता है और इंतज़ार करता है। शुरुआती लोड के बाद भी, भारी पंक्तियाँ स्क्रॉल इनिशियलाइजेशन को सुस्त बना देती हैं क्योंकि पहले कुछ फ्रेम्स सेटअप कार्य में खर्च हो जाते हैं।
प्रति फ्रेम बहुत अधिक काम स्क्रॉल के दौरान स्टटरिंग (stuttering) के रूप में दिखाई देता है। एनिमेशन को स्मूथ रखने के लिए आपके पास प्रति फ्रेम लगभग सोलह मिलीसेकंड का बजट होता है। यदि कोई पंक्ति कंपोनेंट महंगे कैलकुलेशन करता है, रेंडर के अंदर ऑन-द-फ्लाई तारीखों को पार्स करता है, या गहरे ऑब्जेक्ट कंपैरिजन करता है, तो आप उस बजट को पार कर जाते हैं। UI thread फ्रेम्स ड्रॉप कर देता है और उपयोगकर्ता को झटका महसूस होता है।
अत्यधिक मेमोरी का उपयोग एक साइलेंट किलर है। हर नेटिव व्यू की कीमत RAM के रूप में चुकानी पड़ती है। बड़े अनऑप्टिमाइज्ड इमेज, हर कार्ड पर ड्रॉप शैडो, या नेस्टेड टचएबल्स जोड़ें, और मेमोरी का फुटप्रिंट कई गुना बढ़ जाता है। iOS पर सिस्टम बिना चेतावनी के आपके ऐप को बंद कर सकता है। Android पर उपयोगकर्ता लैग को बढ़ते हुए देखता है जब तक कि ऐप उपयोग करने में असमर्थ न लगे।
ऑप्टिमाइज़ेशन चेकलिस्ट
छोटी रणनीतिक आदतें एक ऐसी लिस्ट में अंतर पैदा करती हैं जो केवल काम करती है, और एक ऐसी लिस्ट में जो बहुत स्मूथ चलती है।
Use stable keys. हमेशा अपने डेटा सेट से एक वास्तविक आइडेंटिफायर को key prop में पास करें। कभी भी array index का उपयोग न करें। यदि आपकी लिस्ट रीऑर्डर, फ़िल्टर या आइटम जोड़ती है, तो इंडेक्स-आधारित key React को गलत डेटा को गलत रीसायकल किए गए कंपोनेंट के साथ जोड़ने के लिए भ्रमित कर देती है। वह गलती अनावश्यक unmounts, state mismatches और cascading re-renders को ट्रिगर करती है। एक सही ID React को सटीक रूप से बताती है कि कौन सी row कहाँ स्थानांतरित हुई है।
Memoize rows. अपने row component को React.memo में लपेटें ताकि यह केवल तभी re-render हो जब इसके props वास्तव में बदलें। इस सुरक्षा के बिना, कोई भी parent state update हर दिखाई देने वाली row में render pass को ट्रिगर कर सकता है, भले ही उनका डेटा समान हो। एक लंबी लिस्ट में जो लगातार बदलती रहती है, वे बर्बाद हुए cycles तेज़ी से बढ़ जाते हैं।
Keep renderItem stable. हर parent render पर renderItem prop के अंदर सीधे एक नया फंक्शन परिभाषित करने से बचें। renderItem={({ item }) => <Row data={item} />} जैसा एक inline arrow function हर बार parent अपडेट होने पर एक नया reference बनाता है। FlatList एक बदला हुआ prop देखता है और अनावश्यक रूप से row को रीसायकल कर देता है। render function को component के बाहर परिभाषित करें या useCallback के साथ इसे memoize करें ताकि reference स्थिर रहे।
Use getItemLayout whenever possible. यदि आपकी rows की ऊंचाई निश्चित या अनुमानित है, तो FlatList को सटीक रूप से बताएं कि वह क्या है। यह prop लिस्ट को महंगे native measurement calls को छोड़ने की अनुमति देता है। माउंट होने के बाद प्रत्येक सेल को मापने के बजाय, लिस्ट गणितीय रूप से स्थिति की गणना करती है। यह अंतर विशेष रूप से सैकड़ों या हजारों आइटम वाली लिस्ट में स्पष्ट होता है, जहाँ onLayout chatter JS thread को ठप कर सकता है।
Optimize images aggressively. अनबाउंडेड (Unbounded) इमेज लिस्ट के लिए ज़हर के समान हैं। हमेशा स्पष्ट width और height सेट करें ताकि इमेज डिकोड होने से पहले native layer जगह सुरक्षित कर ले। रिमोट इमेज के लिए, Expo Image जैसी कैशिंग लाइब्रेरी या इसके समकक्ष का उपयोग करें जो memory caching, disk persistence और format optimization को संभालती है। डिफ़ॉल्ट React Native Image component प्रोटोटाइप के लिए काम करता है, लेकिन प्रोडक्शन फीड्स को मेमोरी और लोडिंग स्टेट्स पर अधिक नियंत्रण की आवश्यकता होती है।
Avoid nesting scroll containers. कभी भी एक vertical FlatList को vertical ScrollView के अंदर न रखें। Parent ScrollView सभी scroll events को कैप्चर कर लेता है और child FlatList की अपने viewport को मापने की क्षमता को बाधित करता है। Virtualization टूट जाता है क्योंकि FlatList को अब यह नहीं पता होता कि कौन सी rows दिखाई देनी चाहिए। परिणाम यह होता है कि हर row फिर भी माउंट हो जाती है, जिससे virtualization का पूरा उद्देश्य ही विफल हो जाता है। यदि आपको लिस्ट के ऊपर एक हेडर चाहिए, तो FlatList के अपने ListHeaderComponent prop का उपयोग करें। यदि आपको जटिल sticky behavior चाहिए, तो उचित हेडर कॉन्फ़िगरेशन के साथ SectionList या FlashList का उपयोग करें।
The Golden Rule
यदि कंटेंट छोटा और सीमित है, तो ScrollView को इसे संभालने दें। यदि कंटेंट यूजर-जेनरेटेड डेटा या रिमोट पेजिनेशन के साथ बढ़ता है, तो virtualized list का उपयोग करें। जब लिस्ट ऐप का मुख्य हिस्सा हो और उपयोगकर्ता एक बार में मिनटों तक स्क्रॉल करते हैं, तो FlashList का उपयोग करें।
यहाँ एक आखिरी सच्चाई है जिसे भूलना आसान है। साधारण (Boring) rows तेज़ी से स्क्रॉल होती हैं। आपका row component जितना हल्का होगा, आपकी लिस्ट उतनी ही स्मूथ होगी। व्यक्तिगत row से नेस्टेड नेविगेशन, भारी गणना (heavy computations) और अनावश्यक एनिमेशन को हटा दें। Markup को flat, logic को thin और images को sized रखें। एक लिस्ट इस बात से जीवित रहती है या मर जाती है कि वह क्या रेंडर करती है। प्रत्येक row को हल्का रखें, और लिस्ट बेहतरीन तरीके से प्रीमियम महसूस होगी।
