फीडमध्ये वीस आयटम्स असणे अगदी योग्य वाटते. स्क्रोलिंग अगदी स्मूथ होते. तुमचा क्लायंट खुश असतो. मग तुम्ही ते प्रोडक्शनमध्ये पाठवता, डेटा येतो आणि अचानक तुमच्यासमोर दोन हजार ओळी (rows) येतात. UI अडखळू लागते. मेमरी इतकी वाढते की शेवटी OS ॲप बंद करते. हताश होऊन, काही डेव्हलपर्स सर्व काही एका ScrollView मध्ये गुंडाळतात आणि काम संपल्याचे समजतात. हा निर्णय सहसा एक बग सोडवण्याऐवजी तीन नवीन बग्स निर्माण करतो.

लिस्ट्स हे परफॉर्मन्सचे असे अडथळे (bottleneck) आहेत जे वापरकर्ते तुमच्या React Native ॲपबद्दल काय विचार करतात हे ठरवतात. जर तुम्ही ते योग्यरित्या हाताळले, तर ॲप अगदी नेटिव्ह वाटते. जर ते चुकले, तर सर्वात सुंदर स्क्रीन देखील संथ आणि त्रासदायक बनते. याचे मूळ कारण सहसा तुम्ही निवडलेले कंपोनंट आणि तुम्ही JavaScript आणि UI थ्रेड्सकडून करून घेत असलेल्या कामामधील तफावत हे असते. React Native दोन ट्रॅक्सवर चालते. तुमचे लॉजिक JS thread वर असते, तर पेंटिंग (rendering) native UI thread वर होते. जेव्हा तुम्ही एखादी मोठी लिस्ट चुकीच्या पद्धतीने रेंडर करता, तेव्हा दोन्ही थ्रेड्स लेआउट कॅल्क्युलेशन्स, री-रेंडर्स आणि मेमरी अलोकेशनमध्ये अडकतात. परिणामी फ्रेम्स ड्रॉप होतात, स्क्रीन ब्लँक होते आणि शेवटी ॲप क्रॅश होते.

योग्य साधन निवडा

लिस्ट कंपोनंट निवडणे हा एक विचारपूर्वक घेतलेला आर्किटेक्चरल निर्णय असायला हवा, केवळ सवयीने घेतलेला नाही.

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

FlatList हे लांब आणि एकसमान फीड्ससाठी मुख्य साधन आहे. ते कंटेंट व्हर्च्युअलाइज करते, याचा अर्थ असा की ते सध्या व्ह्यूपोर्टमध्ये दिसणारे किंवा त्याच्या जवळ असलेले रो (rows) देखीलच माउंट करते. वापरकर्ता स्क्रोल करत असताना, FlatList स्क्रीनच्या बाहेर गेलेले सेल्स अनमाउंट करते आणि नवीन डेटासाठी त्यांचा पुनर्वापर (recycle) करते. यामुळे तुमचा ॲरे (array) कितीही मोठा झाला तरी मेमरी स्थिर राहते. जर तुम्ही सोशल टाइमलाइन, नोटिफिकेशन सेंटर किंवा सारख्या कार्ड्सचा सतत स्क्रोल होणारा संग्रह बनवत असाल, तर FlatList हा योग्य डीफॉल्ट पर्याय आहे.

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

FlashList तेव्हा कामाला येते जेव्हा तुम्हाला डिव्हाइसमधून प्रत्येक शेवटचा फ्रेम मिळवायचा असतो. RecyclerListView इकोसिस्टमवर आधारित, हे FlatList पेक्षा अधिक आक्रमकपणे व्ह्यूज रिसायकल करते आणि मध्यम श्रेणीतील हार्डवेअरवर देखील साठ (sixty) फ्रेम्स प्रति सेकंद राखण्याचे लक्ष्य ठेवते. जर तुम्ही हाय-व्हॉल्यूम चॅट इंटरफेस, जलद स्क्रोलिंग असलेले प्रॉडक्ट कॅटलॉग किंवा असा कोणताही स्क्रीन बनवत असाल जिथे स्मूथनेस हा एक स्पर्धात्मक फायदा आहे, तर FlashList वापरणे फायदेशीर ठरेल. हे प्रत्येक स्क्रीनसाठी आवश्यक नाही, परंतु मुख्य अनुभव देणाऱ्या फीड्ससाठी परफॉर्मन्स मधील फरक लक्षणीय असतो.

सामान्य परफॉर्मन्स खराब करणारे घटक

जेव्हा लिस्ट संथ होऊ लागते, तेव्हा तीन मुख्य कारणे असू शकतात.

एकाच वेळी खूप जास्त React ट्रीज माउंट करणे ही सर्वात मोठी समस्या आहे. जेव्हा प्रत्येक रो एक जटिल कंपोनंट ट्री असतो, तेव्हा सुरुवातीचे रेंडरिंग JS thread ला इतका वेळ ब्लॉक करू शकते की स्क्रीन पांढरी दिसते किंवा रेंडरिंगला उशीर होतो. वापरकर्ता ॲप उघडतो आणि वाट पाहतो. सुरुवातीच्या लोड नंतरही, जड (heavy) रोमुळे स्क्रोल इनिशियलायझेशन संथ होते कारण सुरुवातीचे काही फ्रेम्स सेटअप कामात खर्च होतात.

प्रति फ्रेम जास्त काम केल्यामुळे स्क्रोलिंग दरम्यान अडखळल्यासारखे (stuttering) वाटते. ॲनिमेशन स्मूथ ठेवण्यासाठी तुमच्याकडे प्रति फ्रेम साधारण सोळा मिलीसेकंदचा वेळ असतो. जर एखादा रो कंपोनंट महागडी कॅल्क्युलेशन्स करत असेल, ऑन-द-फ्लाई तारखांचे पार्सिंग करत असेल किंवा रेंडरमध्ये डीप ऑब्जेक्ट कंपॅरिजन करत असेल, तर तुम्ही हा वेळ ओलांडता. UI thread फ्रेम्स ड्रॉप करते आणि वापरकर्त्याला झटका बसल्यासारखे वाटते.

अति मेमरी वापर हा एक 'सायलेंट किलर' आहे. प्रत्येक नेटिव्ह व्ह्यूसाठी RAM खर्च होतो. मोठ्या अनऑप्टिमाइझ्ड इमेजेस, प्रत्येक कार्डवर ड्रॉप शॅडो किंवा नेस्टेड टचेबल्स जोडल्यास मेमरीचा वापर प्रचंड वाढतो. iOS वर सिस्टम कोणत्याही पूर्वसूचनेशिवाय तुमचे ॲप बंद करू शकते. Android वर वापरकर्त्याला ॲप वापरण्याअयोग्य होईपर्यंत लॅग वाढत असल्याचे दिसते.

ऑप्टिमायझेशन चेकलिस्ट

काही लहान धोरणात्मक सवयी एका साध्या काम करणाऱ्या लिस्टला आणि अत्यंत वेगाने चालणाऱ्या लिस्टला वेगळे करतात.

स्थिर (stable) keys वापरा. तुमच्या डेटा सेटमधील एक खरा आयडेंटिफायर नेहमी key prop ला पास करा. कधीही array index वापरू नका. जर तुमची लिस्ट रीऑर्डर (reorder), फिल्टर किंवा नवीन आयटम्स जोडत असेल, तर index-आधारित key मुळे React चुकीच्या डेटाची जोडी चुकीच्या रीसायकल केलेल्या (recycled) कंपोनंटसोबत लावतो. या चुकीमुळे अनावश्यक unmounts, state mismatches आणि cascading re-renders होतात. एक योग्य ID React ला नेमके कोणते रो (row) कुठे हलले आहे हे सांगते.

Rows मेमोइझ (Memoize) करा. तुमचा row component React.memo मध्ये गुंडाळा (wrap करा), जेणेकरून त्याचे props खरोखर बदलले तरच तो re-render होईल. या सुरक्षेशिवाय, पालकाचा (parent) कोणताही state update प्रत्येक दृश्यमान (visible) row मध्ये render pass ट्रिगर करू शकतो, जरी त्यांचा डेटा सारखाच असला तरीही. मोठ्या लिस्टमध्ये, हे वाया गेलेले cycles वेगाने वाढतात.

renderItem स्थिर ठेवा. प्रत्येक parent render वेळी renderItem prop मध्ये थेट नवीन फंक्शन परिभाषित करणे टाळा. renderItem={({ item }) => <Row data={item} />} सारखे inline arrow function प्रत्येक वेळी parent अपडेट होताना एक नवीन reference तयार करते. FlatList ला बदललेला prop दिसतो आणि तो विनाकारण row रीसायकल करतो. render function कंपोनंटच्या बाहेर परिभाषित करा किंवा useCallback वापरून ते मेमोइझ करा जेणेकरून reference स्थिर राहील.

शक्य असेल तेव्हाच getItemLayout वापरा. जर तुमच्या rows ची उंची निश्चित (fixed) किंवा अंदाजित असेल, तर FlatList ला ती नेमकी किती आहे ते सांगा. हा prop लिस्टला महागड्या (expensive) native measurement calls टाळण्यास मदत करतो. mount झाल्यानंतर प्रत्येक सेल मोजण्याऐवजी, लिस्ट गणितीय पद्धतीने (mathematically) पोझिशन मोजते. शेकडो किंवा हजारो आयटम्स असलेल्या लिस्टमध्ये हा फरक प्रकर्षाने जाणवतो, जिथे onLayout मुळे होणारी गडबड JS thread ला संथ करू शकते.

इमेजेसचे (images) आक्रमकपणे ऑप्टिमायझेशन करा. अनबाउंडेड (Unbounded) इमेजेस लिस्टसाठी घातक असतात. नेहमी स्पष्ट width आणि height सेट करा जेणेकरून इमेज डिकोड होण्यापूर्वीच native layer जागा राखून ठेवेल. रिमोट इमेजेससाठी, Expo Image किंवा तत्सम caching library वापरा जी memory caching, disk persistence आणि format optimization हाताळते. डिफॉल्ट React Native Image component प्रोटोटाइपसाठी ठीक आहे, परंतु प्रोडक्शन फीडसाठी memory आणि loading states वर अधिक नियंत्रणाची गरज असते.

Scroll containers एकमेकांत नेस्ट (nest) करणे टाळा. कधीही vertical FlatList ला vertical ScrollView च्या आत ठेवू नका. Parent ScrollView सर्व scroll events पकडते आणि child FlatList ची viewport मोजण्याची क्षमता विस्कळीत करते. यामुळे virtualization बिघडते कारण FlatList ला आता कोणते rows दिसले पाहिजेत हे समजत नाही. परिणामी, प्रत्येक row mount होतोच, ज्यामुळे virtualization चा मूळ उद्देशच संपतो. जर तुम्हाला लिस्टच्या वर header हवे असेल, तर FlatList चा स्वतःचा ListHeaderComponent prop वापरा. जर तुम्हाला जटिल sticky behavior हवे असेल, तर योग्य header configuration सह SectionList किंवा FlashList वापरा.

सुवर्ण नियम (The Golden Rule)

जर कंटेंट कमी आणि मर्यादित असेल, तर ScrollView चा वापर करा. जर कंटेंट युजर-जनरेटेड डेटा किंवा रिमोट पेजिनेशनमुळे वाढत असेल, तर virtualized list वापरा. जेव्हा लिस्ट ही ॲपचा मुख्य भाग असते आणि युजर्स मिनिटांनु मिनिटे स्क्रोल करणार असतात, तेव्हा FlashList चा वापर करा.

एक शेवटचे सत्य जे विसरणे सोपे आहे: साधे (boring) rows वेगाने स्क्रोल होतात. तुमचा row component जितका हलका असेल, तितकी तुमची लिस्ट स्मूथ असेल. प्रत्येक row मधून नेस्टेड नेव्हिगेशन (nested navigations), जड कम्प्युटेशन्स (heavy computations) आणि अनावश्यक ॲनिमेशन्स काढून टाका. Markup सपाट (flat) ठेवा, लॉजिक हलके ठेवा आणि इमेजेस योग्य आकारात ठेवा. लिस्टचे यश किंवा अपयश हे ती काय रेंडर करते त्यातील एकूण वजनावर (cumulative weight) अवलंबून असते. प्रत्येक row हलका (cheap) ठेवा, आणि तुमची लिस्ट सर्वोत्तम प्रकारे प्रीमियम (expensive) वाटेल.