Takip et

React Native’de Verimli Rendering: Performans Optimizasyonu Rehberi

React Native’de Verimli Rendering: Performans Optimizasyonu Rehberi React Native ile mobil uygulama geliştirirken performans, kullanıcı deneyimini doğrudan etkileyen kritik bir faktördür.

React Native’de Verimli Rendering: Performans Optimizasyonu Rehberi

React Native ile mobil uygulama geliştirirken performans, kullanıcı deneyimini doğrudan etkileyen kritik bir faktördür. Uygulamanız ne kadar hızlı ve akıcı çalışırsa, kullanıcılar da o kadar memnun kalır. Bu makalede, React Native’in rendering (görüntüleme) iş akışını derinlemesine inceleyecek ve uygulamanızın performansını en üst düzeye çıkarmak için kullanabileceğiniz etkili yöntemleri adım adım öğreneceksiniz. Özellikle karmaşık listeler, animasyonlar ve yoğun veri işleme senaryolarında performans düşüşlerinin önüne nasıl geçebileceğinizi keşfedeceğiz.

React Native Rendering Temellerini Anlamak

React Native’de rendering süreci, uygulamanızın kullanıcı arayüzünün (UI) nasıl oluşturulduğunu ve güncellendiğini ifade eder. Temelde React’in sanal DOM (Document Object Model) prensibine dayanır, ancak mobil platformlar için optimize edilmiştir. React Native, JavaScript kodunuzu alır ve bunu native platform bileşenlerine (iOS için UIKit, Android için Android UI) dönüştürür. Bu dönüşüm, “Bridge” adı verilen bir arayüz üzerinden gerçekleşir. JavaScript thread’i, UI thread’inden ayrı çalışır. Bu ayrım, JavaScript’te yoğun işlemler yapıldığında bile UI’ın donmasını engeller, ancak aynı zamanda iki thread arasındaki iletişimde bir darboğaz oluşturabilir. Bu nedenle, rendering sürecini optimize etmek, bu köprüdeki iletişimi en aza indirmeyi ve gereksiz güncellemeleri önlemeyi hedefler.

Rendering’in iki ana aşaması vardır: ilk render (initial render) ve re-render (yeniden render). İlk render, bileşenlerin ekrana ilk kez çizildiği zamandır. Re-render ise, bileşenin state’i (durumu) veya props’ları (özellikleri) değiştiğinde tetiklenir. Amacımız, gereksiz re-render’ları önleyerek performansı artırmaktır. Bir bileşenin re-render olması için, state veya props’larından birinin değişmesi gerekir. Ancak bazen, bu değişiklikler doğrudan bileşeni etkilemese bile re-render tetiklenebilir. İşte bu noktada optimizasyon teknikleri devreye girer.

React Native’in rendering mimarisi, JavaScript kodu ile native UI arasında bir köprü görevi gören bir “Bridge” üzerine kuruludur. JavaScript thread’i, uygulamanızın mantığını ve UI güncellemelerini yönetir. UI thread’i ise, ekrandaki görsel öğelerin çizilmesinden sorumludur. Bu iki thread’in ayrı çalışması, JavaScript tarafında yoğun bir işlem olduğunda bile uygulamanın donmasını engellemeye yardımcı olur. Ancak, bu köprü üzerinden yapılan veri aktarımı ve iletişim, performans darboğazlarına yol açabilir. Bu nedenle, rendering sürecini optimize etmek, bu köprüdeki veri akışını en aza indirmek ve gereksiz güncellemeleri engellemekle yakından ilgilidir.

Her bileşen, bir kez oluşturulduktan sonra, state’i veya props’ları değiştiğinde “re-render” olur. Bu, bileşenin tekrar çizilmesi anlamına gelir. Temel hedefimiz, yalnızca gerçekten gerekli olduğunda re-render işlemini tetiklemektir. Gereksiz yere re-render olan bileşenler, hem CPU kaynaklarını boşa harcar hem de uygulamanın genel performansını düşürür. Bu durum, özellikle karmaşık UI’lar veya sık güncellenen veri akışlarında daha belirgin hale gelir.

Performans Sorunlarının Kaynakları: Neden Yavaşlıyoruz?

React Native uygulamalarında performans sorunlarının birçok farklı kaynağı olabilir. En yaygın olanlarından biri, gereksiz re-render’lardır. Bir bileşenin state’i veya props’u değiştiğinde, React bu bileşenin ve onun alt bileşenlerinin güncellenmesi gerektiğini düşünür. Ancak bazen, props’lar veya state’teki değişiklikler aslında UI’ı doğrudan etkilemez. Bu durumda bile, bileşenler yeniden render edilir ve bu da performans kaybına yol açar. Özellikle büyük ve iç içe geçmiş bileşen ağaçlarında, tek bir gereksiz re-render bile zincirleme re-render’lara neden olabilir.

Bir diğer önemli sorun, büyük ve karmaşık listelerin işlenmesidir. Eğer bir listede yüzlerce veya binlerce öğe varsa ve bu öğelerin her biri karmaşık bir yapıya sahipse, tüm listeyi her seferinde yeniden render etmek ciddi performans sorunlarına yol açar. Ekranın yalnızca görünür kısmındaki öğelerin render edilmesi ve kaydırma sırasında yeni öğelerin dinamik olarak oluşturulması gerekir. Bu, “Virtualization” (sanal erişim) olarak adlandırılır ve React Native’de FlatList ve SectionList gibi bileşenlerle sağlanır. Ancak bu bileşenlerin de doğru yapılandırılmaması performansı olumsuz etkileyebilir.

Bellek sızıntıları (memory leaks) da performans düşüşlerinin başka bir yaygın nedenidir. Eğer bir bileşen DOM’dan kaldırıldığında bellekteki referanslarını düzgün bir şekilde temizlemezse, bu bellekte kalır ve zamanla uygulamanın bellek kullanımını artırır. Bu durum, özellikle uzun süre çalışan uygulamalarda veya sık sık ekranlar arasında geçiş yapıldığında belirginleşir. Bu sızıntılar, uygulamanın yavaşlamasına, çökmesine ve genel olarak kararsız hale gelmesine neden olabilir.

Ayrıca, JavaScript thread’inde uzun süren işlemler de UI thread’ini bloke edebilir ve uygulamanın donmasına sebep olabilir. Örneğin, büyük bir veri setini işlemek, karmaşık hesaplamalar yapmak veya ağdan büyük miktarda veri indirmek gibi işlemler, UI’ın yanıt vermesini engelleyebilir. Bu tür işlemlerin arka planda veya ayrı bir thread’de yapılması, performansın korunması açısından hayati önem taşır.

Son olarak, animasyonların verimsiz kullanımı da performans sorunlarına yol açabilir. Karmaşık veya çok sayıda animasyonun aynı anda çalıştırılması, UI thread’ini yoğun bir şekilde meşgul edebilir. React Native’in sunduğu Animated API’si veya react-native-reanimated gibi kütüphaneler, animasyonları daha verimli hale getirmek için tasarlanmıştır. Ancak bu araçların doğru ve optimize edilmiş bir şekilde kullanılması, performansın korunması için kritiktir.

React.memo ve useMemo ile Gereksiz Re-render’ları Engellemek

React Native’de gereksiz re-render’ları önlemenin en etkili yollarından biri React.memo ve useMemo hook’larını kullanmaktır. React.memo, bir React bileşenini saran bir yüksek dereceli bileşendir (Higher-Order Component – HOC). Bu bileşen, props’ları değişmediği sürece bileşenin yeniden render edilmesini engeller. Yani, bir bileşenin props’ları aynı kaldığı sürece, React.memo o bileşeni tekrar render etmek yerine önceki render edilen sonucunu kullanır. Bu, özellikle aynı props’larla sık sık render edilen veya render maliyeti yüksek olan bileşenler için büyük bir performans artışı sağlayabilir.

Örneğin, bir liste öğesi bileşeni düşünelim. Eğer bu liste öğesinin içeriği yalnızca dışarıdan gelen data prop’una bağlıysa ve bu data prop’u değişmediği sürece liste öğesinin kendisinin yeniden render edilmesine gerek yoksa, React.memo kullanmak mantıklıdır. Bunu şu şekilde uygulayabiliriz:

import React from 'react';

const MyListItem = React.memo(({ item }) => {
  console.log('MyListItem render ediliyor'); // Bu log sadece item değiştiğinde görünecektir
  return (
    // Liste öğesinin JSX'i
    
      {item.title}
      {item.description}
    
  );
});

// ... ana bileşenizde
 }
  keyExtractor={(item) => item.id}
/>

Burada MyListItem bileşeni React.memo ile sarılmıştır. Bu sayede, FlatList yeniden render olsa bile, item prop'u değişmediği sürece MyListItem yeniden render edilmeyecektir. Bu, özellikle büyük listelerde önemli bir performans kazancı sağlar.

useMemo ise, hesaplama maliyeti yüksek olan değerleri memoize (önbelleğe alma) etmek için kullanılan bir React hook'udur. Bu hook, bir fonksiyonun sonucunu önbelleğe alır ve bağımlılıkları değişmediği sürece fonksiyonu tekrar çalıştırmaz. Bu, özellikle nesnelerin veya dizilerin oluşturulması gibi maliyetli işlemler için kullanışlıdır.

Bir örnekle açıklayalım: Diyelim ki bir bileşende, gelen bir diziyi işleyerek yeni bir obje oluşturuyorsunuz ve bu obje başka bir yerde kullanılıyor. Eğer bu dizi her render'da aynı kalıyorsa, her seferinde bu objeyi yeniden oluşturmak gereksizdir. useMemo ile bu işlemi optimize edebiliriz:

import React, { useMemo } from 'react';

const MyComponent = ({ dataArray }) => {
  const processedData = useMemo(() => {
    console.log('processedData hesaplanıyor'); // Bu log sadece dataArray değiştiğinde görünecektir
    // Maliyetli işlem: dataArray'i işle
    return dataArray.map(item => item * 2);
  }, [dataArray]); // Bağımlılık: dataArray değiştiğinde yeniden hesapla

  return (
    
      {/* processedData'yı kullanan JSX */}
      {processedData.map(value => {value})}
    
  );
};

Bu örnekte, processedData değişkeni useMemo ile memoize edilmiştir. dataArray değişmediği sürece, processedData'nın yeniden hesaplanması engellenir. Bu, özellikle karmaşık veri dönüşümleri veya filtrelemeleri söz konusu olduğunda performansı önemli ölçüde artırabilir.

useCallback ise useMemo'ya benzer şekilde fonksiyonları memoize etmek için kullanılır. Bir fonksiyonun referansının değişmemesini sağlar. Bu, özellikle çocuk bileşenlere callback fonksiyonları props olarak geçirilirken önemlidir. Eğer bir callback fonksiyonu, useCallback ile memoize edilmezse, ana bileşen her render olduğunda fonksiyonun yeni bir referansı oluşturulur. Bu da, React.memo ile optimize edilmiş çocuk bileşenlerin gereksiz yere yeniden render olmasına neden olabilir.

Örneğin, bir butonun onPress olayını yönetirken:

import React, { useCallback } from 'react';

const ParentComponent = () => {
  const handleClick = useCallback(() => {
    console.log('Butona tıklandı!');
  }, []); // Bağımlılık dizisi boş, fonksiyon hiç değişmeyecek

  return (
    
      
    
  );
};

const ChildButton = React.memo(({ onClick }) => {
  console.log('ChildButton render ediliyor');
  return 

Burada handleClick fonksiyonu useCallback ile memoize edildiği için, ParentComponent yeniden render olduğunda handleClick fonksiyonunun referansı değişmez. Bu da ChildButton'ın React.memo sayesinde gereksiz yere yeniden render edilmesini engeller.

Bu üç mekanizma – React.memo, useMemo ve useCallback – React Native uygulamanızın rendering performansını optimize etmek için güçlü araçlardır. Ancak, her yerde kullanmak yerine, yalnızca gerçekten performans sorunu yaratan veya maliyeti yüksek olan bileşenler ve hesaplamalar için kullanmak en doğrusudur. Aşırı kullanım, kodun okunabilirliğini azaltabilir ve beklenmedik davranışlara yol açabilir.

FlatList ve SectionList ile Verimli Listeler Oluşturmak

React Native'de büyük veri kümelerini görüntülerken performansın en çok etkilendiği alanlardan biri listelerdir. Eğer bir listede yüzlerce, hatta binlerce öğe varsa, tüm öğeleri tek seferde DOM'a eklemek uygulamanızı yavaşlatabilir ve hatta çökertme noktasına getirebilir. İşte tam bu noktada FlatList ve SectionList gibi sanallaştırma (virtualization) yeteneklerine sahip bileşenler devreye girer. Bu bileşenler, yalnızca ekranda görünür olan öğeleri render ederek bellek kullanımını ve işlem yükünü önemli ölçüde azaltır.

FlatList, düz bir veri dizisini göstermek için kullanılır. En temel haliyle data, renderItem ve keyExtractor prop'larına ihtiyaç duyar.

* data: Gösterilecek öğelerin dizisi.
* renderItem: Her bir öğeyi nasıl render edeceğimizi belirten bir fonksiyon. Bu fonksiyon, { item, index } gibi parametreler alır ve bir React öğesi döndürür.
* keyExtractor: Her bir öğe için benzersiz bir anahtar (key) döndüren bir fonksiyon. Bu, React'in öğeleri verimli bir şekilde güncellem
esi ve silmesi için kritiktir. Genellikle öğenin id'si kullanılır.

Örnek bir FlatList kullanımı:

import React from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';

const DATA = [
  { id: '1', title: 'İlk Öğe' },
  { id: '2', title: 'İkinci Öğe' },
  // ... binlerce öğe olabilir
];

const renderItem = ({ item }) => (
  
    {item.title}
  
);

const App = () => {
  return (
     item.id}
    />
  );
};

const styles = StyleSheet.create({
  item: {
    backgroundColor: '#f9c2ff',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
  },
  title: {
    fontSize: 24,
  },
});

export default App;

FlatList'in performansını daha da artırmak için kullanabileceğiniz bazı önemli prop'lar şunlardır:

* getItemLayout: Bu prop, her bir öğenin yüksekliğini (veya genişliğini, yatay listelerde) bilmenizi sağlar. Bu bilgi, FlatList'in öğelerin konumunu daha hızlı hesaplamasına yardımcı olur ve kaydırma performansını iyileştirir. Özellikle sabit yükseklikteki öğeler için çok etkilidir.

getItemLayout={(data, index) => (
      {length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
    )}


Burada ITEM_HEIGHT sabit öğe yüksekliğidir.

* removeClippedSubviews: Bu prop, varsayılan olarak true'dur ve ekranın dışında kalan öğelerin native alt görünümlerini kaldırır. Bu, bellek kullanımını azaltır. Ancak, bazen bu prop'un false yapılması bazı durumlarda sorunları çözebilir (nadiren de olsa).

* initialNumToRender: Başlangıçta render edilecek öğe sayısı. Bu değeri, ekranı dolduracak kadar bir değere ayarlamak, kullanıcının ilk gördüğünüzde boş ekranla karşılaşmasını engeller.

* maxToRenderPerBatch: Her bir render partisindeki öğe sayısı. Kaydırma sırasında daha akıcı bir deneyim için bu değeri ayarlayabilirsiniz.

* windowSize: Görünür alanın her iki tarafında ne kadar öğenin render edileceğini belirler. Daha büyük bir windowSize, kaydırma sırasında daha az boşluk görünmesini sağlar ancak bellek kullanımını artırır.

* updateCellsBatchingPeriod: Yeni öğelerin gruplar halinde render edilme süresi (milisaniye).

SectionList, veriyi başlıklar altında gruplandırarak göstermek için kullanılır. FlatList'e benzer şekilde çalışır ancak sections prop'unu alır. sections prop'u, her biri bir title (başlık) ve data (o başlığa ait öğeler) içeren nesnelerden oluşan bir dizidir.

import React from 'react';
import { SectionList, View, Text, StyleSheet } from 'react-native';

const SECTIONS = [
  {
    title: 'Bölüm 1',
    data: [{ id: '1a', text: 'Öğe 1A' }, { id: '1b', text: 'Öğe 1B' }],
  },
  {
    title: 'Bölüm 2',
    data: [{ id: '2a', text: 'Öğe 2A' }, { id: '2b', text: 'Öğe 2B' }],
  },
];

const renderItem = ({ item }) => (
  
    {item.text}
  
);

const renderSectionHeader = ({ section: { title } }) => (
  
    {title}
  
);

const App = () => {
  return (
     item.id + index}
      renderItem={renderItem}
      renderSectionHeader={renderSectionHeader}
    />
  );
};

const styles = StyleSheet.create({
  item: {
    backgroundColor: '#e9e9e9',
    padding: 10,
    marginVertical: 5,
    marginHorizontal: 10,
  },
  header: {
    fontSize: 24,
    backgroundColor: '#f0f0f0',
    padding: 10,
    marginVertical: 5,
    marginHorizontal: 10,
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
  },
  text: {
    fontSize: 16,
  },
});

export default App;

SectionList için de FlatList'teki performans optimizasyon prop'larının çoğu geçerlidir.

Gerçek dünya senaryoları:

1. E-ticaret Ürün Listesi: Bir e-ticaret uygulamasında binlerce ürün gösteren bir liste düşünün. Her ürünün resmi, adı, fiyatı ve belki de kısa bir açıklaması var. Bu durumda FlatList kullanmak ve renderItem fonksiyonunda her bir ürün kartını optimize etmek önemlidir. getItemLayout prop'unu kullanarak liste öğelerinin sabit boyutlu olmasını sağlamak, kaydırmayı çok daha akıcı hale getirecektir. Ayrıca, ürün görselleri için Image bileşeninin performansını optimize etmek (örneğin, react-native-fast-image gibi kütüphanelerle) de önemlidir.
2. Sosyal Medya Akışı: Bir sosyal medya uygulamasında kullanıcıların gönderilerini gösteren bir akış. Her gönderi metin, resim, video ve yorumlar içerebilir. Bu karmaşık yapıya sahip öğeler için FlatList'in sanallaştırma özelliği hayati önem taşır. React.memo'yu her bir gönderi bileşeni için kullanarak, yalnızca gönderinin içeriği değiştiğinde yeniden render olmasını sağlayabiliriz.
3. Ayarlar Ekranı: Bir mobil uygulamanın ayarlar ekranında genellikle gruplandırılmış birçok seçenek bulunur. Bu tür bir yapı için SectionList idealdir. Her bir ayar grubu bir bölüm (section) olarak temsil edilebilir.

FlatList ve SectionList, React Native'de liste performansını optimize etmek için vazgeçilmez araçlardır. Ancak, doğru yapılandırılmadıklarında veya içlerindeki öğeler optimize edilmediğinde hala performans sorunlarına yol açabilirler. Bu nedenle, bu bileşenleri kullanırken yukarıda bahsedilen prop'ları dikkatlice gözden geçirmek ve uygulamanızın özel ihtiyaçlarına göre ayarlamak önemlidir.

Native Thread'leri Anlamak ve JSI ile İletişimi Optimize Etmek

React Native'in mimarisinde, JavaScript kodu ve native UI kodları ayrı thread'lerde çalışır. JavaScript thread'i uygulamanın mantığını, state yönetimini ve UI güncellemelerinin talep edilmesini yönetirken, UI thread'i (veya native thread'ler) bu talepleri alıp ekrandaki görsel öğeleri çizer. Bu iki dünya arasındaki iletişim, "Bridge" adı verilen bir mekanizma üzerinden gerçekleşir. Geleneksel olarak, bu iletişim JSON mesajlarının seri hale getirilip gönderilmesiyle olurdu. Bu, özellikle sık ve büyük veri aktarımlarında bir darboğaz oluşturabilir, çünkü seri hale getirme ve tersine çevirme işlemleri zaman alıcıdır.

Bu darboğazı aşmak için React Native, JavaScript Interface (JSI) adı verilen yeni ve daha verimli bir iletişim katmanı geliştirmiştir. JSI, JavaScript ve native kodun doğrudan birbirini çağırmasına olanak tanır, JSON'a bağımlılığı ortadan kaldırır ve iletişimi önemli ölçüde hızlandırır. JSI, C++'ta yazılmış bir köprüdür ve JavaScript motoru ile native platform arasında daha düşük seviyeli bir erişim sağlar. Bu, daha hızlı veri aktarımı, daha az bellek kullanımı ve daha akıcı animasyonlar anlamına gelir.

JSI'nın getirdiği en önemli yeniliklerden biri, Native Modules'lerin (native modüller) ve UI Manager'ın çalışma şeklini değiştirmesidir. Geleneksel Bridge ile, bir JavaScript fonksiyonu bir native modülü çağırdığında, bu çağrı seri hale getirilir, Bridge üzerinden gönderilir, native tarafta alınır, tersine çevrilir ve çalıştırılırdı. JSI ile, bu çağrılar doğrudan yapılır. Bu, özellikle performans açısından kritik olan animasyonlar ve yoğun veri güncellemeleri için büyük bir avantajdır.

JSI'nın sunduğu bir diğer önemli özellik ise TurboModules ve Fabric'tir.

* TurboModules: Geleneksel native modüllerin JSI üzerinde yeniden tasarlanmış halidir. JSI sayesinde daha verimli bir şekilde yüklenirler ve çağrılırlar. TurboModules, native modüllerin "lazy loading" (tembel yükleme) özelliğini de destekler, yani yalnızca gerçekten ihtiyaç duyulduğunda yüklenirler. Bu, uygulamanın başlangıç süresini iyileştirebilir.

* Fabric: React Native'in yeni rendering engine'idir. Fabric, JSI üzerine inşa edilmiştir ve daha verimli, senkron ve paralel bir rendering süreci sunar. Fabric ile, JavaScript ve native UI arasındaki etkileşim daha hızlı ve daha akıcı hale gelir. Örneğin, animasyonlar artık JavaScript thread'ini bloke etmeden doğrudan native tarafta işlenebilir. Bu, daha akıcı animasyonlar, daha iyi dokunma tepkileri ve genel olarak daha duyarlı bir kullanıcı arayüzü anlamına gelir.

Peki, bu JSI, TurboModules ve Fabric'i bizim gibi geliştiriciler nasıl kullanıyor? Aslında, çoğu durumda doğrudan bu teknolojilerle uğraşmak zorunda değiliz. React Native'in güncel sürümleri bu yeni mimariyi varsayılan olarak kullanır veya geçişi kolaylaştırır. Ancak, bu teknolojilerin varlığından haberdar olmak ve performans sorunlarıyla karşılaştığımızda bu mimarinin nasıl çalıştığını bilmek önemlidir.

Örneğin, react-native-reanimated gibi kütüphaneler, JSI'nın sunduğu avantajları kullanarak animasyonları doğrudan native tarafta işler. Bu sayede, JavaScript thread'ini meşgul etmeden çok daha akıcı ve karmaşık animasyonlar oluşturmak mümkün olur. Eğer uygulamanızda yoğun animasyonlar kullanıyorsanız, react-native-reanimated gibi JSI tabanlı kütüphaneleri tercih etmek, performansınızı önemli ölçüde artıracaktır.

Gerçek dünya senaryosu: Bir harita uygulamasında kullanıcıyı takip eden bir animasyon düşünün. Eski Bridge mimarisiyle, her konum güncellemesi için JavaScript'ten native'e veri gönderilmesi ve ardından haritanın güncellenmesi gerekirdi. Bu, özellikle yüksek frekanslı güncellemelerde takılmalara neden olabilirdi. Fabric ve JSI ile, konum güncellemeleri daha doğrudan ve verimli bir şekilde işlenebilir, bu da daha akıcı bir takip animasyonu sağlar.

Kullanıcı deneyimi açısından bakıldığında, JSI ve Fabric, uygulamanızın "hissettirdiği" performansı doğrudan etkiler. Daha hızlı tepki veren butonlar, akıcı kaydırmalar ve pürüzsüz animasyonlar, kullanıcıların uygulamanızı daha profesyonel ve keyifli bulmasını sağlar. Bu nedenle, React Native'in bu temel mimari değişikliklerini anlamak, daha iyi performanslı uygulamalar geliştirmek için kritik öneme sahiptir.

Bellek Yönetimi ve Sızıntıları Önleme

Performans optimizasyonunda bellek yönetimi, genellikle göz ardı edilen ancak kritik bir konudur. React Native uygulamalarında bellek sızıntıları (memory leaks), zamanla uygulamanın bellek kullanımını artırarak yavaşlamasına, kararsız hale gelmesine ve hatta çökmesine neden olabilir. Bu nedenle, bellek sızıntılarını önlemek ve mevcut belleği verimli kullanmak, uygulamanın genel sağlığı için hayati önem taşır.

Bellek sızıntısı, bir nesnenin artık kullanılmadığı halde bellekte tutulması durumudur. React Native'de bu genellikle şu senaryolarda ortaya çıkar:

1. Dinleyicilerin (Listeners) Temizlenmemesi: Bir bileşen mount edildiğinde (ekrana eklendiğinde) bir olay dinleyicisi eklediyseniz ve bu bileşen unmount edildiğinde (ekrandan kaldırıldığında) bu dinleyiciyi temizlemezseniz, dinleyici bellekte kalır. Bu, özellikle useEffect hook'unda yaygındır. useEffect'in geri dönüş fonksiyonu, bileşen unmount edildiğinde çalışır ve temizlik işlemlerini yapmak için idealdir.

import React, { useEffect } from 'react';
    import { DeviceEventEmitter } from 'react-native';

    const MyComponent = () => {
      useEffect(() => {
        const subscription = DeviceEventEmitter.addListener('someEvent', () => {
          // Olay dinleyici
        });

        // Cleanup fonksiyonu: Bileşen unmount edildiğinde çalışır
        return () => {
          subscription.remove(); // Dinleyiciyi kaldır
          console.log('MyComponent unmount edildi ve dinleyici kaldırıldı.');
        };
      }, []); // Boş bağımlılık dizisi, sadece mount ve unmount'ta çalışır

      return ;
    };


Burada subscription.remove() çağrısı, useEffect'in geri dönüş fonksiyonunda yapıldığı için, bileşen ekrandan kaldırıldığında olay dinleyicisi bellekten temizlenir.

2. Zamanlayıcıların (Timers) Temizlenmemesi: setTimeout veya setInterval gibi zamanlayıcılar kullanıldığında, bu zamanlayıcıların da temizlenmesi gerekir. Eğer bir bileşen unmount edildiğinde çalışan bir zamanlayıcı varsa ve bu zamanlayıcı temizlenmezse, zamanlayıcı bellekte kalır ve potansiyel olarak hatalara yol açar.

import React, { useEffect, useState } from 'react';

    const TimerComponent = () => {
      const [count, setCount] = useState(0);

      useEffect(() => {
        const intervalId = setInterval(() => {
          setCount(prevCount => prevCount + 1);
        }, 1000);

        // Cleanup fonksiyonu
        return () => {
          clearInterval(intervalId); // Zamanlayıcıyı temizle
          console.log('TimerComponent unmount edildi ve zamanlayıcı kaldırıldı.');
        };
      }, []);

      return Sayaç: {count};
    };

3. Referansların (References) Temizlenmemesi: Bir bileşen içinde oluşturulan ve referansları tutulan nesnelerin, artık ihtiyaç duyulmadığında serbest bırakılması gerekir. Özellikle karmaşık veri yapıları veya global durum yönetimi kullanılıyorsa bu önemlidir.

4. Detached DOM Elements (Bağlantısız DOM Öğeleri): React Native'de bu durum, native UI öğelerinin bellekten düzgün bir şekilde kaldırılmaması anlamına gelebilir. removeClippedSubviews gibi prop'lar bu sorunu çözmeye yardımcı olur.

Bellek sızıntılarını tespit etmek için React Native Debugger veya Flipper gibi araçları kullanabilirsiniz. Bu araçlar, uygulamanızın bellek kullanımını izlemenize ve potansiyel sızıntıları belirlemenize olanak tanır.

Bellek kullanımını optimize etmek için genel ipuçları:

* Gereksiz Nesnelerden Kaçının: Her render'da yeni nesneler veya diziler oluşturmak yerine, useMemo ve useCallback ile bunları memoize edin.
* Büyük Veri Setlerini Yönetme: FlatList ve SectionList gibi sanallaştırma bileşenlerini kullanın.
* Resim Optimizasyonu: Büyük resimleri kullanmaktan kaçının, doğru boyutlarda ve formatlarda optimize edilmiş resimler kullanın. react-native-fast-image gibi kütüphaneler, resim yükleme ve önbellekleme performansını artırabilir.
* Global Durum Yönetimi: Redux, Zustand veya Context API gibi global durum yönetimi çözümlerini kullanırken, gereksiz güncellemeleri ve büyük veri depolamayı önlemek için dikkatli olun. Sadece gerekli olan verileri store'da tutun.
* Component Lifecycle'ını Anlamak: Bileşenlerin mount, update ve unmount aşamalarını iyi anlayın ve temizlik işlemlerini doğru yerlerde yapın.

Bellek sızıntıları, uygulamanızın gizli düşmanıdır. Düzenli olarak bellek kullanımını izlemek ve yukarıda belirtilen temizlik prensiplerine uymak, uzun vadede daha kararlı ve performanslı bir uygulama elde etmenizi sağlayacaktır.

Gerçek Dünya Senaryoları: Vaka Analizleri

Bu bölümde, React Native'de rendering ve performans optimizasyonunun gerçek dünyada nasıl uygulandığına dair iki vaka analizi sunacağız. Bu analizler, yukarıda bahsedilen tekniklerin pratik uygulamalarını ve karşılaşılan zorlukları göstermeyi amaçlamaktadır.

Vaka Analizi 1: Yoğun Veri Gösteren Bir Finansal Uygulama

Senaryo: Bir finansal analiz uygulaması geliştiriliyor. Uygulama, kullanıcılara hisse senedi fiyatları, grafikler, haber akışları ve piyasa verileri gibi çok sayıda dinamik veriyi gerçek zamanlı olarak göstermeli. Veri akışı oldukça yoğun ve sürekli güncelleniyor.

Sorunlar:
* Binlerce hisse senedini listeleyen bir ekranda kaydırma performansı düşüktü.
* Gerçek zamanlı fiyat güncellemeleri, uygulamanın genelinde gereksiz re-render'lara neden oluyordu.
* Karmaşık grafiklerin yüklenmesi ve güncellenmesi zaman alıyordu.
* Uygulamanın bellek kullanımı zamanla artıyordu.

Çözümler ve Uygulanan Teknikler:
1. FlatList Optimizasyonu: Hisse senedi listesi için FlatList kullanıldı. getItemLayout prop'u, her hisse senedi öğesinin sabit bir yüksekliğe sahip olduğu varsayılarak uygulandı. Bu, kaydırma performansını ciddi şekilde iyileştirdi.
2. React.memo Kullanımı: Her bir hisse senedi öğesi bileşeni React.memo ile sarıldı. Bu sayede, yalnızca hisse senedinin kendi verileri (fiyat, değişim vb.) değiştiğinde ilgili öğe yeniden render oldu. Diğer veriler (örneğin, haber başlıkları) değişse bile hisse senedi öğesi etkilenmedi.
3. useMemo ve useCallback ile Durum Yönetimi: Gerçek zamanlı veri güncellemeleri için kullanılan state yönetimi (örneğin, Redux veya Context API) optimize edildi. Gereksiz güncellemeleri önlemek için useMemo ile hesaplamalar memoize edildi ve useCallback ile callback fonksiyonlarının referansları sabit tutuldu. Bu, gereksiz re-render'ların zincirleme etkisini azalttı.
4. Grafik Optimizasyonu: Grafik çizimi için react-native-svg ve react-native-chart-kit gibi kütüphaneler kullanıldı. Grafikler, yalnızca görünür olduklarında veya verileri gerçekten değiştiğinde güncellenecek şekilde ayarlandı. useMemo ile grafik verilerinin işlenmesi optimize edildi.
5. Bellek Yönetimi: useEffect hook'larındaki tüm dinleyiciler ve zamanlayıcılar, bileşen unmount edildiğinde temizlendi. Büyük veri setlerinin bellekte gereksiz yere tutulmasını önlemek için, kullanılmayan veriler düzenli olarak temizlendi.
6. JSI ve Fabric'ten Yararlanma: Uygulamanın güncel React Native sürümleriyle geliştirilmesi ve react-native-reanimated gibi JSI tabanlı kütüphanelerin kullanılması, animasyonların ve UI etkileşimlerinin daha akıcı olmasını sağladı.

Sonuç: Bu optimizasyonlar sayesinde, hisse senedi listesindeki kaydırma performansı %70'ten fazla arttı. Gerçek zamanlı veri güncellemeleri daha az CPU kullanırken, uygulamanın genel yanıt verme süresi iyileşti. Bellek sızıntıları büyük ölçüde azaltıldı ve uygulama daha kararlı hale geldi.

Vaka Analizi 2: Sosyal Medya İçerik Akışı ve Etkileşimler

Senaryo: Bir sosyal medya uygulamasının ana akış ekranı. Kullanıcılar gönderileri kaydırabilir, beğenebilir, yorum yapabilir ve paylaşabilir. Gönderiler metin, resim, video ve anket gibi çeşitli içerik türlerini barındırabilir.

Sorunlar:
* Akışta kaydırma sırasında takılmalar ve gecikmeler yaşanıyordu.
* Video oynatımı ve resim yüklemeleri performansı olumsuz etkiliyordu.
* Beğenme, yorum yapma gibi etkileşimler bazen yavaş yanıt veriyordu.
* Uygulama uzun süre kullanıldığında pil tüketimi artıyordu.

Çözümler ve Uygulanan Teknikler:
1. Sanallaştırılmış Liste: FlatList kullanıldı ve initialNumToRender, maxToRenderPerBatch, windowSize gibi prop'lar optimize edilerek akıcı bir kaydırma deneyimi sağlandı.
2. React.memo ile Gönderi Bileşeni Optimizasyonu: Her bir gönderi bileşeni React.memo ile optimize edildi. Bu, yalnızca gönderinin içeriği (metin, resim URL'si, video URL'si vb.) değiştiğinde gönderinin yeniden render edilmesini sağladı. Beğenme veya yorum sayısındaki değişiklikler gibi durum güncellemeleri, gönderi bileşenini gereksiz yere yeniden render etmiyordu.
3. Video ve Resim Optimizasyonu: Video oynatımı için react-native-video ve resimler için react-native-fast-image gibi optimize edilmiş kütüphaneler kullanıldı. Videolar ve resimler yalnızca görünür olduklarında yüklendi ve oynatıldı. Arka planda oynatılan veya görünmeyen videolar durduruldu.
4. **useCallback ile Et

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.