Bir akışta yirmi öğe mükemmel hissettirir. Kaydırma pürüzsüzdür. Müşteriniz mutludur. Sonra canlıya alırsınız, veriler gelir ve aniden iki bin satıra bakakalırsınız. Arayüz takılmaya başlar. Bellek kullanımı, işletim sistemi uygulamayı kapatana kadar artar. Çaresizlik içinde bazı geliştiriciler her şeyi bir ScrollView içine sarar ve işi bitirir. Bu karar genellikle çözdüğü her bir hata için üç yeni hata doğurur.
Listeler, kullanıcıların React Native uygulamanızı nasıl algıladığını belirleyen performans darboğazıdır. Onları doğru yaparsanız uygulama yerel (native) hissettirir. Yanlış yaparsanız en güzel ekran bile bir çileye dönüşür. Temel neden genellikle seçtiğiniz bileşen ile JavaScript ve UI thread'lerinden yapmalarını istediğiniz iş arasındaki uyumsuzluktur. React Native iki hat üzerinde çalışır. Mantığınız JS thread üzerinde yaşarken, çizim işlemleri native UI thread üzerinde gerçekleşir. Devasa bir listeyi yanlış şekilde render ettiğinizde, her iki thread de yerleşim hesaplamaları, yeniden render'lar (re-renders) ve bellek tahsisleri (memory allocations) içinde boğulmaya başlar. Sonuç; düşen kareler (dropped frames), beyaz ekran parlamaları ve sonunda bir çökmedir.
Doğru Aracı Seçin
Bir liste bileşeni seçmek bir refleks değil, bilinçli bir mimari karar olmalıdır.
ScrollView en basit seçenektir. Ona verdiğiniz her bir alt bileşeni alır, her birini anında belleğe yükler (mount eder) ve tüm yığını native kaydırma motoruna iletir. Bu, ayarlar ekranı, giriş formu veya on bölümlü statik bir ürün detay sayfası gibi kısa ve sabit içerikler için tam olarak istediğiniz şeydir. Tahmin edilebilirdir ve stillendirmesi kolaydır. Sorun ise sanallaştırma (virtualization) özelliğinin olmamasıdır. Eğer ona iki bin öğe verirseniz, itaatkar bir şekilde iki bin tane native view oluşturacaktır. Büyük veya dinamik veri setleri için ScrollView kullanmayın. Onu bir kütüphane rafı olarak değil, çerçevelenmiş bir poster olarak düşünün.
FlatList, uzun ve tek tip akışlar için iş beygiridir. İçeriği sanallaştırır; yani yalnızca o anda görünür olan veya viewport'a yakın olan satırları belleğe yükler. Kullanıcı kaydırdıkça, FlatList ekrandan çıkan hücrelerin bağlantısını keser (unmount eder) ve onları gelen veriler için yeniden kullanır (recycle). Bu, dizinizin ne kadar büyüdüğünden bağımsız olarak bellek kullanımını sabit tutar. Bir sosyal zaman tüneli, bildirim merkezi veya benzer kartlardan oluşan sürekli kaydırılabilir bir koleksiyon oluşturuyorsanız, FlatList varsayılan doğru seçimdir.
SectionList, organizasyon yeteneğine sahip bir FlatList'tir. Verileriniz alfabetik olarak sıralanmış bir adres defteri, tarihe göre ayrılmış bir antrenman günlüğü veya aya göre düzenlenmiş bir fatura listesi gibi gruplandırılmış paketler halinde geldiğinde kullanın. Yapışkan (sticky) bölüm başlıklarını render eder ve gruplandırma mantığını sizin yerinize yönetir. Arka planda FlatList ile aynı sanallaştırma motorunu kullanır, böylece başlıklı bölümlerin eklediği yapı ile aynı bellek avantajlarını elde edersiniz.
FlashList, cihazdan son kareye kadar her şeyi süzmek istediğinizde devreye girer. RecyclerListView ekosistemi üzerine inşa edilmiştir, görünümleri FlatList'ten daha agresif bir şekilde yeniden kullanır ve orta segment donanımlarda bile saniyede altmış kare (60 FPS) hızını korumayı hedefler. Yüksek hacimli bir sohbet arayüzü, hızlı kaydırma hızına sahip bir ürün kataloğu veya akıcılığın rekabet avantajı olduğu herhangi bir ekran oluşturuyorsanız, FlashList ek bağımlılığa (dependency) değer. Her ekran için gerekli değildir, ancak temel deneyimi belirleyen akışlar için performans farkı fark edilir düzeydedir.
Yaygın Performans Katilleri
Bir liste ağırlaşmaya başladığında genellikle üç şüpheli vardır.
Aynı anda çok fazla React ağacı (tree) yüklemek (mount etmek) en dramatik hatadır. Her satır karmaşık bir bileşen ağacı olduğunda, ilk render işlemi JS thread'ini boş bir beyaz ekran veya çirkin bir gecikmeli ilk boyama (first paint) oluşturacak kadar uzun süre bloke edebilir. Kullanıcı uygulamayı açar ve bekler. İlk yüklemeden sonra bile, ağır satırlar kaydırma başlatma işlemini hantallaştırır çünkü ilk birkaç kare kurulum işleri tarafından tüketilir.
Kare başına düşen aşırı iş yükü, kaydırma sırasında takılmalar (stuttering) olarak kendini gösterir. Animasyonları akıcı tutmak için kare başına yaklaşık on altı milisaniyelik bir bütçeniz vardır. Eğer bir satır bileşeni maliyetli hesaplamalar yapıyorsa, tarihleri anlık olarak ayrıştırıyorsa (parse) veya render içinde derin nesne karşılaştırmaları (deep object comparisons) gerçekleştiriyorsa, bu bütçeyi aşarsınız. UI thread kareleri düşürür ve kullanıcı bir sarsıntı hisseder.
Aşırı bellek kullanımı sessiz katildir. Her native view RAM maliyeti yaratır. Optimize edilmemiş büyük görseller, her kartta gölgeler (drop shadows) veya iç içe geçmiş dokunulabilir alanlar (nested touchables) eklerseniz, bellek ayak izi katlanarak artar. iOS'ta sistem uygulamanızı uyarı vermeden sonlandırabilir. Android'de ise kullanıcı, uygulama kullanılamaz hale gelene kadar gecikmenin (lag) artışını izler.
Optimizasyon Kontrol Listesi
Küçük stratejik alışkanlıklar, sadece çalışan bir listeyi, uçuşa geçen bir listeden ayırır.
Kararlı anahtarlar (keys) kullanın. Veri kümenizden gelen gerçek bir tanımlayıcıyı her zaman key prop'una geçirin. Asla dizi indeksini kullanmayın. Listeniz yeniden sıralanırsa, filtrelenirse veya öğeler eklenirse, indeks tabanlı bir anahtar React'ı yanlış veriyi yanlış geri dönüştürülmüş (recycled) bileşenle eşleştirmeye zorlar. Bu hata; gereksiz unmount işlemlerini, durum (state) uyumsuzluklarını ve zincirleme yeniden render (re-render) işlemlerini tetikler. Doğru bir ID, React'a tam olarak hangi satırın nereye taşındığını söyler.
Satırları memoize edin. Satır bileşeninizi React.memo ile sarmalayın, böylece yalnızca prop'ları gerçekten değiştiğinde yeniden render edilir. Bu koruma olmadan, verileri aynı olsa bile herhangi bir üst (parent) state güncellemesi, görünürdeki her satırda bir render geçişini tetikleyebilir. Sürekli güncellenen uzun bir listede, bu boşa harcanan döngüler hızla birikir.
renderItem'ı kararlı tutun. Her üst render işleminde renderItem prop'u içinde doğrudan yeni bir fonksiyon tanımlamaktan kaçının. renderItem={({ item }) => <Row data={item} />} gibi satır içi (inline) bir ok fonksiyonu, üst bileşen her güncellendiğinde yeni bir referans oluşturur. FlatList değişen bir prop görür ve satırı gereksiz yere geri dönüştürür. Referansın kararlı kalması için render fonksiyonunu bileşenin dışında tanımlayın veya useCallback ile memoize edin.
Mümkün olduğunca getItemLayout kullanın. Satırlarınız sabit veya öngörülebilir bir yüksekliğe sahipse, bunu FlatList'e tam olarak bildirin. Bu prop, listenin maliyetli yerel (native) ölçüm çağrılarını atlamasını sağlar. Liste, her hücreyi mount işleminden sonra ölçmek yerine, konumu matematiksel olarak hesaplar. Bu fark, özellikle yüzlerce veya binlerce öğesi olan ve onLayout trafiğinin JS thread'ini felç edebileceği listelerde çok belirgindir.
Görüntüleri agresif bir şekilde optimize edin. Sınırlandırılmamış görüntüler, liste performansının zehridir. Yerel katmanın görüntü çözülmeden (decode) önce yer ayırması için her zaman açık bir genişlik ve yükseklik belirleyin. Uzaktaki görüntüler için bellek önbelleğe alma (memory caching), disk kalıcılığı (disk persistence) ve format optimizasyonunu yöneten Expo Image gibi bir önbelleğe alma kütüphanesi veya eşdeğeri kullanın. Varsayılan React Native Image bileşeni prototipler için iş görür, ancak üretim (production) ortamındaki veri akışları bellek ve yükleme durumları üzerinde daha fazla kontrol gerektirir.
İç içe kaydırma konteynerlarından kaçının. Dikey bir FlatList'i asla dikey bir ScrollView içine yerleştirmeyin. Üstteki ScrollView tüm kaydırma olaylarını yakalar ve alttaki FlatList'in viewport'unu ölçme yeteneğini bozar. FlatList hangi satırların görünür olması gerektiğini artık bilemediği için sanallaştırma (virtualization) bozulur. Sonuç olarak, her satır yine de mount edilir ve sanallaştırmanın tüm amacı boşa gider. Bir listenin üstünde bir başlığa ihtiyacınız varsa, FlatList'in kendi ListHeaderComponent prop'unu kullanın. Karmaşık yapışkan (sticky) davranışlara ihtiyacınız varsa, uygun başlık yapılandırmasıyla bir SectionList veya FlashList kullanın.
Altın Kural
İçerik küçük ve sınırlıysa, işi ScrollView'a bırakın. İçerik kullanıcı tarafından oluşturulan verilerle veya uzaktan sayfalama (pagination) ile büyüyorsa, sanallaştırılmış bir liste kullanın. Liste uygulamanın merkezindeyse ve kullanıcılar dakikalarca kaydırma yapacaksa, FlashList'e yönelin.
Unutulması kolay olan son bir gerçek daha var: Sade satırlar hızlı kayar. Satır bileşeniniz ne kadar hafif olursa, listeniz o kadar akıcı olur. Tekil satırdan iç içe geçmiş navigasyonları, ağır hesaplamaları ve gereksiz animasyonları ayıklayın. Markup'ı düz, mantığı yalın ve görüntü boyutlarını sabit tutun. Bir liste, render ettiği şeylerin kümülatif ağırlığıyla yaşar veya ölür. Her satırı hafif tutun, böylece liste mümkün olan en iyi şekilde üst düzey bir his verecektir.
