Takip et

React Render Optimizasyonunda Uzmanlaşma: Memoization’dan Üretim Kalıplarına

React uygulamalarınızın beklenenden daha yavaş çalıştığını fark ettiniz mi? Modern web geliştirmenin vazgeçilmezi React’te performans, kullanıcı deneyimini doğrudan etkileyen kritik bir faktördür. Bu kapsamlı rehberde, React render optimizasyonunun derinliklerine inecek, memoization tekniklerinden gerçek dünya üretim kalıplarına kadar her aşamayı adım adım inceleyerek uygulamalarınızın hızını ve tepki süresini nasıl artırabileceğinizi keşfedeceksiniz.

React, dinamik kullanıcı arayüzleri oluşturmak için güçlü bir kütüphane olsa da, yanlış kullanıldığında veya optimize edilmediğinde performans sorunlarına yol açabilir. Temelinde React, “Virtual DOM” adı verilen bir mekanizma kullanarak DOM manipülasyonlarını minimize eder. Bir bileşenin durumu veya props’ları değiştiğinde, React bu bileşeni ve genellikle alt bileşenlerini de yeniden render eder. Bu sürece “reconciliation” denir ve React, yeni Virtual DOM ağacı ile bir önceki ağacı karşılaştırarak yalnızca gerekli DOM güncellemelerini yapar.

Ancak, Virtual DOM oldukça hızlı olsa da, bir bileşenin gereksiz yere render edilmesi bile performansı olumsuz etkileyebilir. Özellikle karmaşık bileşen ağaçlarında, küçük bir değişiklik yüzlerce bileşenin yeniden hesaplanmasına yol açabilir. Bu durum, kullanıcının arayüzde takılmalar, gecikmeler veya yavaş yükleme süreleri yaşamasına neden olur. Kullanıcı deneyimi açısından bakıldığında, geciken her milisaniye potansiyel müşteri kaybı veya kullanıcı memnuniyetsizliği anlamına gelebilir. E-ticaret siteleri için bu, terk edilmiş alışveriş sepetlerine, SaaS uygulamaları için ise düşük etkileşime yol açabilir. İşte bu noktada, render optimizasyonu devreye girer. Amacımız, React’in yalnızca gerçekten değişen veya değişmesi gereken bileşenleri yeniden render etmesini sağlamaktır. Bunu yaparak, CPU kullanımını azaltır, pil ömrünü korur ve uygulamanın genel tepki süresini artırırız. Birçok geliştirici, “React hızlıdır, optimizasyona gerek yok” yanılgısına kapılsa da, uygulamanız ölçeklendikçe ve veri karmaşıklığı arttıkça, bu düşünce ciddi sorunlara neden olabilir.

Gereksiz render’lar, sadece hesaplama gücünü tüketmekle kalmaz, aynı zamanda JavaScript çalışma zamanında da ek yük oluşturur. Bu, özellikle düşük güçlü cihazlar veya yavaş internet bağlantıları kullanan mobil kullanıcılar için belirgin bir sorun haline gelir. Dolayısıyla, React uygulamalarınızın üretim ortamında beklenen performansı sergilemesini istiyorsanız, render optimizasyonu bir “olmalı” değil, bir “zorunluluktur”. Performans iyileştirmeleri, sadece daha hızlı bir uygulama anlamına gelmez; aynı zamanda daha ölçeklenebilir, sürdürülebilir ve bakımı daha kolay bir kod tabanı anlamına da gelir. İlerleyen bölümlerde, bu gereksiz render’ları nasıl tespit edeceğimizi ve hangi stratejilerle engelleyebileceğimizi detaylıca inceleyeceğiz.

Gereksiz Render’ları Tespit Etmenin Yolları Nelerdir?

Optimizasyon sürecinin ilk ve en önemli adımı, performans darboğazlarını doğru bir şekilde teşhis etmektir. Nereye odaklanmanız gerektiğini bilmeden rastgele optimizasyonlar yapmak, genellikle zaman kaybına ve bazen daha kötü performansa yol açabilir. React ekosistemi, bu tespiti kolaylaştırmak için güçlü araçlar sunar.

React DevTools Profiler ile Derinlemesine Analiz Nasıl Yapılır?

React DevTools, tarayıcınızın geliştirici araçlarına entegre olan bir eklentidir ve React uygulamalarınızın çalışma zamanı davranışını anlamanıza yardımcı olur. Özellikle “Profiler” sekmesi, hangi bileşenlerin ne sıklıkta ve ne kadar sürede render edildiğini görselleştirmek için paha biçilmezdir.

Bir performans kaydı başlattığınızda, React DevTools, uygulamanızdaki her render döngüsünü kaydeder. Kaydı durdurduğunuzda, bir “alev grafiği” veya “sıralı grafik” şeklinde görsel bir özet sunar. Alev grafiğinde, her çubuk bir bileşeni temsil eder ve genişliği, o bileşenin render süresiyle orantılıdır. Uzun ve sık görünen çubuklar, potansiyel darboğazları gösterir. Bir bileşene tıkladığınızda, o bileşenin props’larının veya state’inin neden değiştiğini gösteren “Render Nedeni” bölümünü de görebilirsiniz. Bu, gereksiz render’ların kök nedenini anlamak için hayati bir bilgidir.

Örneğin, bir e-ticaret uygulamasında, kullanıcı ürün filtrelerini değiştirirken tüm ürün listesinin (binlerce ürün kartının) yeniden render edildiğini fark edebilirsiniz. Profiler, bu “ÜrünKartı” bileşenlerinin her birinin neden yeniden render edildiğini gösterebilir. Belki de üst bileşenden gelen bir prop, değer olarak aynı kalsa bile referansı değiştiği için tüm alt bileşenlerin yeniden render edilmesine neden oluyordur.

Uzman İpucu: Profiler’ı kullanırken “Highlight updates when components render” seçeneğini etkinleştirin. Bu, uygulamanız çalışırken yeniden render edilen bileşenleri görsel olarak mavi bir kutuyla çevreleyerek anında geri bildirim sağlar.

Neden Yeniden Render Ediliyor? why-did-you-render Kütüphanesi Ne İşe Yarar?

React DevTools güçlü olsa da, bazen bir bileşenin neden yeniden render edildiğini tam olarak anlamak zordur. İşte bu noktada

why-did-you-render

kütüphanesi devreye girer. Bu geliştirme bağımlılığı, bileşenlerinizin ne zaman ve neden yeniden render edildiğini konsola loglayarak size çok daha spesifik bilgiler sunar.

Kurulumu oldukça basittir:


import React from 'react';

if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React, {
    trackAllPureComponents: true, // Tüm PureComponent'leri ve React.memo bileşenlerini izler
    trackHooks: true, // Hook'ları da izler
    logOnDifferentValues: true, // Sadece değerler farklı olduğunda loglar
    // Diğer seçenekler...
  });
}
    

Bu kodu genellikle uygulamanızın ana giriş dosyasının (örn. index.js) başına, geliştirme ortamında çalışacak şekilde eklersiniz. Ardından, izlemek istediğiniz herhangi bir bileşene whyDidYouRender = true; prop'unu ekleyerek veya trackAllPureComponents: true ile tüm saf bileşenleri izleyerek detaylı loglar alabilirsiniz.

Konsolda, bir bileşenin neden yeniden render edildiğini göreceksiniz: "props değişti", "state değişti", "hook bağımlılıkları değişti" gibi açıklamalarla birlikte, eski ve yeni değerler arasındaki farkları gösteren detaylı tablolar sunar. Örneğin, bir önceki "ÜrünKartı" örneğinde,

why-did-you-render

, prop olarak geçen bir objenin referansının değiştiğini, ancak içindeki tüm alanların aynı kaldığını gösterebilir. Bu,

React.memo

gibi optimizasyon tekniklerini uygulamak için size doğrudan bir hedef verir.

Vaka Analizi: Büyük Bir E-ticaret Uygulamasında Ürün Listesi Optimizasyonu

Gerçek bir senaryoyu ele alalım: Binlerce ürünün listelendiği, sürekli filtrelenen ve sıralanan bir e-ticaret sayfası. Kullanıcı her filtreyi değiştirdiğinde veya arama kutusuna yazı yazdığında, sayfa belirgin şekilde takılıyor ve yavaşlıyor.

  1. Tespit: İlk olarak React DevTools Profiler'ı açtık. Kullanıcı bir filtre uyguladığında, tüm "ÜrünListesi" bileşeninin ve içerisindeki her "ÜrünKartı" bileşeninin yeniden render edildiğini gördük. Grafikteki uzun çubuklar ve yüksek render süreleri dikkat çekiciydi.
  2. Derinlemesine İnceleme: "ÜrünKartı" bileşenine tıkladığımızda, profilin "Render Nedeni" bölümünde, üst bileşenden gelen 'ürün' objesinin her seferinde yeni bir referansla geçtiğini gördük. Ürün verileri aslında değişmemişti, ancak liste filtresi uygulandığında, üst bileşen yeni bir ürün dizisi oluşturduğu için, her bir ürün objesi de yeni bir referansla "ÜrünKartı"na iletiliyordu.
  3. Çözüm Önerisi: Bu durumda,
    React.memo

    kullanarak "ÜrünKartı" bileşenini optimize etmek mantıklı olacaktır. Eğer bir "ÜrünKartı"nın props'ları (ürün bilgisi) aslında değişmediyse, yeniden render edilmesini engelleyebiliriz.

Bu vaka analizi, doğru araçlarla problemin kökünü bulmanın ve hedefe yönelik çözümler üretmenin ne kadar önemli olduğunu göstermektedir. Optimizasyon her zaman "daha hızlı kod yazmak" anlamına gelmez; çoğu zaman "gereksiz işi yapmaktan kaçınmak" anlamına gelir.

Memoization Nedir ve React'te Nasıl Kullanılır? (React.memo, useCallback, useMemo)

Memoization, bilgisayar bilimlerinde yaygın olarak kullanılan bir optimizasyon tekniğidir. Bir fonksiyonun veya bileşenin daha önce hesaplanmış sonuçlarını, aynı girdilerle tekrar çağrıldığında kullanmak üzere önbelleğe alması prensibine dayanır. React bağlamında, bu, bileşenlerin veya pahalı hesaplamaların gereksiz yere tekrar yürütülmesini önleyerek performansı önemli ölçüde artırabilir.

React.memo ile Bileşenlerin Yeniden Render Edilmesini Nasıl Engelleriz?

React.memo

, fonksiyonel bileşenler için bir higher-order component (HOC) olarak işlev görür. Bir bileşeni

React.memo

ile sarmaladığınızda, React, bu bileşenin props'larının bir önceki render ile aynı olup olmadığını kontrol eder. Eğer props'lar değişmemişse, React bileşeni yeniden render etmek yerine, son render edilmiş çıktısını kullanır. Bu, özellikle sık güncellenen üst bileşenlerin altındaki "saf" (pure) bileşenler için çok etkilidir.


import React from 'react';

const UrunKart = ({ urunAdi, fiyat, resimUrl }) => {
  console.log(${urunAdi} render edildi);
  return (
    
{urunAdi}

{urunAdi}

{fiyat} TL

); }; // React.memo ile UrunKart bileşenini sarmalıyoruz const MemoizedUrunKart = React.memo(UrunKart); export default MemoizedUrunKart;

Yukarıdaki örnekte,

MemoizedUrunKart

bileşeni, props'ları değişmediği sürece yeniden render edilmeyecektir. Varsayılan olarak,

React.memo

props'ları sığ (shallow) bir şekilde karşılaştırır. Yani, objeler veya diziler için sadece referans eşitliğini kontrol eder. Eğer props'larda karmaşık objeler veya diziler varsa ve bunların içeriğinin değişip değişmediğine göre render'ı kontrol etmek istiyorsanız,

React.memo

'nun ikinci argümanı olarak özel bir karşılaştırma fonksiyonu sağlayabilirsiniz:


const MemoizedUrunKart = React.memo(UrunKart, (prevProps, nextProps) => {
  // Sadece urunAdi ve fiyat değiştiyse render et
  return prevProps.urunAdi === nextProps.urunAdi && prevProps.fiyat === nextProps.fiyat;
});
    

Ancak, her bileşeni

React.memo

ile sarmalamak her zaman en iyi çözüm değildir. Props karşılaştırma işlemi de kendi başına bir maliyet gerektirir. Eğer bir bileşen çok sık değişen props'lara sahipse veya render maliyeti çok düşükse,

React.memo

kullanmanın getirdiği ek yük, sağladığı faydadan daha fazla olabilir. Akıllıca ve hedefe yönelik kullanılmalıdır.

useCallback ile Fonksiyon Referanslarını Nasıl Optimize Ederiz?

React'te fonksiyonlar birer objedir ve her render'da yeniden oluşturulurlar. Bu durum, özellikle bu fonksiyonlar alt bileşenlere prop olarak geçirildiğinde sorun yaratabilir.

React.memo

ile optimize edilmiş bir alt bileşen, kendisine prop olarak geçen bir fonksiyonun referansı her render'da değiştiği için gereksiz yere yeniden render edilebilir. İşte burada

useCallback

hook'u devreye girer.

useCallback

, bir fonksiyonu bağımlılıkları değişmediği sürece önbelleğe almanızı sağlar. Böylece, her render'da aynı fonksiyon referansını elde edersiniz ve bu da alt bileşenlerin gereksiz yere yeniden render edilmesini engeller.


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

const UrunListesi = () => {
  const [sayac, setSayac] = useState(0);
  const [urunler, setUrunler] = useState([{ id: 1, ad: 'Klavye', fiyat: 150 }]);

  const handleSepeteEkle = useCallback((urunId) => {
    console.log(${urunId} sepete eklendi!);
    // Sepet state'ini güncelleme logic'i
  }, []); // Bağımlılık dizisi boş olduğu için bu fonksiyon sadece bir kez oluşturulur

  return (
    
{urunler.map(urun => ( handleSepeteEkle(urun.id)} /> ))}
); };

Yukarıdaki örnekte,

handleSepeteEkle

fonksiyonu

useCallback

ile sarmalanmıştır. Bağımlılık dizisi boş olduğu için, bu fonksiyon

UrunListesi

bileşeni her render edildiğinde yeniden oluşturulmayacak, referansı sabit kalacaktır. Eğer

MemoizedUrunKart

bileşeni

onSepeteEkle

prop'unu kullanıyorsa ve bu prop her render'da yeni bir referansla geliyorsa,

MemoizedUrunKart

da gereksiz yere render edilirdi.

useCallback

bu sorunu çözer.

useMemo ile Pahalı Hesaplamaları Nasıl Önbelleğe Alırız?

useMemo

,

useCallback

'e benzer, ancak fonksiyon referanslarını önbelleğe almak yerine, bir fonksiyonun döndürdüğü değeri önbelleğe alır. Bu, bileşen render'ı sırasında yapılan pahalı hesaplamaların (örneğin, büyük bir diziyi filtreleme, sıralama veya karmaşık bir matematiksel işlem) yalnızca bağımlılıkları değiştiğinde yeniden yapılmasını sağlayarak performansı artırır.


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

const FiltrelenmisUrunler = ({ tumUrunler, aramaTerimi }) => {
  // useMemo ile pahalı filtreleme işlemini önbelleğe alıyoruz
  const filtrelenmisListe = useMemo(() => {
    console.log('Pahalı filtreleme işlemi yapılıyor...');
    return tumUrunler.filter(urun =>
      urun.ad.toLowerCase().includes(aramaTerimi.toLowerCase())
    );
  }, [tumUrunler, aramaTerimi]); // tumUrunler veya aramaTerimi değiştiğinde yeniden hesapla

  return (
    
{filtrelenmisListe.map(urun => (

{urun.ad} - {urun.fiyat} TL

))}
); };

Yukarıdaki örnekte,

filtrelenmisListe

değişkeninin değeri,

tumUrunler

veya

aramaTerimi

değişmediği sürece yeniden hesaplanmayacaktır. Bu,

FiltrelenmisUrunler

bileşeni üst bileşenin state'i değiştiği için yeniden render olsa bile, filtreleme işleminin gereksiz yere tekrarlanmamasını sağlar.

Hem

useCallback

hem de

useMemo

dikkatli kullanılmalıdır. Her zaman optimizasyon sağlamazlar ve yanlış kullanıldığında, önbelleğe alma işleminin kendi maliyeti, hesaplamanın maliyetinden daha fazla olabilir. Genel kural, yalnızca belirgin bir performans sorunu gördüğünüzde veya bir bileşenin memoization stratejisi ile birlikte kullanmanız gerektiğinde bunları uygulamaktır.

Performans Canavarı useReducer ve Context API ile Optimizasyon Mümkün mü?

React'te state yönetimi, özellikle büyük ve karmaşık uygulamalarda performansı doğrudan etkileyen bir alandır.

useState

basit senaryolar için harika olsa da, daha karmaşık state mantığı veya global state yönetimi gerektiğinde

useReducer

ve Context API kombinasyonu önemli performans avantajları sağlayabilir.

useReducer, useState'den Neden Daha İyi Olabilir?

useState

, basit anahtar/değer çiftleri veya küçük objeler için idealdir. Ancak, bir state'in güncellenmesi başka bir state'in veya birden fazla state alanının aynı anda güncellenmesini gerektirdiğinde, veya state güncelleme mantığı karmaşıklaştığında,

useReducer

daha temiz ve performanslı bir çözüm sunabilir.

Temel fark,

useState

'in genellikle her state güncellemesi için bir fonksiyon çağrısı gerektirmesidir. Birden fazla

useState

kullanımı, art arda state güncellemelerine ve potansiyel olarak birden fazla render'a yol açabilir (React genellikle bunları gruplasa da).

useReducer

ise, tüm state güncellemelerini tek bir dispatch mekanizması altında toplar ve genellikle tek bir render döngüsünde birden fazla state alanını günceller. Bu, özellikle birbirine bağımlı state'leri güncellerken gereksiz ara render'ları önleyebilir.


import React, { useReducer } from 'react';

const initialState = { count: 0, showText: true };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    case 'toggleText':
      return { ...state, showText: !state.showText };
    default:
      throw new Error();
  }
}

const SayacVeMetin = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    

Sayı: {state.count}

{state.showText && 'Bu bir metin!'}

); };

Burada,

count

ve

showText

aynı reducer içinde yönetilmekte ve bu da state yönetimini merkezileştirmektedir. Karmaşık formlar, oyunlar veya birden fazla bağımlı state alanı olan uygulamalar için

useReducer

okunabilirliği ve performansıyla öne çıkar.

Context API ve Render Performansı Arasındaki İlişkiyi Nasıl Yönetiriz?

Context API, React'te prop drilling'i önlemek ve global state'i kolayca paylaşmak için mükemmel bir araçtır. Ancak, doğru kullanılmadığında ciddi performans sorunlarına yol açabilir. Bir Context'in değeri değiştiğinde, o Context'i tüketen tüm bileşenler (alt bileşenleri dahil) yeniden render edilir, bu da genellikle gereksiz render'lara yol açar.

Örneğin, bir kullanıcı Context'iniz varsa ve bu Context, kullanıcı adı, profil resmi, yetkiler gibi birçok alanı içeriyorsa, sadece kullanıcının avatarı değiştiğinde dahi, tüm Context'i tüketen bileşenler yeniden render edilir. Bu, büyük ölçekli uygulamalarda kolayca bir performans darboğazı haline gelebilir.

Selektörler ile Context Kullanımında Render Optimizasyonu Mümkün mü?

Bu sorunu aşmak için "selektörler" (selectors) kullanmak etkili bir stratejidir. Selektörler, Context'ten yalnızca ihtiyacınız olan belirli parçayı çekerek, sadece o parça değiştiğinde bileşenin yeniden render edilmesini sağlar. Bunu genellikle birden fazla Context oluşturarak veya bir özel hook yazarak başarırız.

Yöntem 1: Birden Fazla Context Kullanımı
Global state'inizi mantıksal olarak ayrı Context'lere bölerek, her Context'in sadece ilgili kısımlarını güncellemesini sağlayabilirsiniz. Örneğin,

UserAuthContext

ve

UserProfileContext

gibi iki ayrı Context oluşturabilirsiniz. Kullanıcının sadece profil bilgileri değiştiğinde, sadece

UserProfileContext

'i tüketen bileşenler render edilir.

Yöntem 2: Özel Selektör Hook'ları Kullanımı
Bu, genellikle daha esnek ve güçlü bir yaklaşımdır.

useContext

hook'unun doğrudan döndürdüğü değeri kullanmak yerine, Context'i tüketen bir özel hook yazabilir ve bu hook içinde

useMemo

kullanarak sadece ilgili state parçasını seçebilirsiniz. Ancak,

useContext

hook'u her render'da Context değerini döndürdüğü için, selector fonksiyonu da her render'da çalışır ve bu pek verimli değildir.

Daha gelişmiş bir yaklaşım, React'in kendi

useContextSelector

önerisi (henüz resmi bir hook değil, ancak bazı kütüphanelerde mevcut) veya

zustand

veya

redux

gibi harici state yönetim kütüphanelerinin selector yeteneklerini kullanmaktır. React'in kendi ekosisteminde

use-context-selector

gibi kütüphaneler bu soruna çözüm sunar:


// use-context-selector kütüphanesinden örnek
import { createContext, useContextSelector } from 'use-context-selector';

const GlobalStateContext = createContext(null);

// Örneğin, bu Context'i sağlayan bir provider
const GlobalStateProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    
      {children}
    
  );
};

// Sadece 'userName' prop'unu tüketen bir bileşen
const UserGreeting = () => {
  // Sadece state.userName değiştiğinde bu bileşen yeniden render olur
  const userName = useContextSelector(GlobalStateContext, (state) => state.userName);
  return 

Merhaba, {userName}!

; }; // Sadece 'notifications' prop'unu tüketen başka bir bileşen const NotificationCount = () => { const notifications = useContextSelector(GlobalStateContext, (state) => state.notifications); return

Bildirimler: {notifications.length}

; };

Bu yaklaşımla, Context'in tamamı değişse bile, bileşenler sadece kendi ilgilendikleri state parçası değiştiğinde yeniden render edilir. Bu, büyük ve global state'i olan uygulamalarda Context API'nın performans etkisini minimize etmek için kritik bir adımdır.

Vaka Analizi: Büyük Ölçekli Bir Dashboard Uygulamasında Global State Yönetimi

Bir B2B dashboard uygulamasını düşünelim. Bu uygulama, kullanıcı ayarları, bildirimler, aktif projeler ve analiz verileri gibi birçok global state'i yönetiyor. Başlangıçta tüm global state tek bir Context içinde tutuluyordu. Sonuç: Kullanıcının küçük bir ayarı değiştiğinde bile, dashboard'daki tüm widget'lar ve navigasyon çubukları gereksiz yere yeniden render ediliyordu, bu da yavaş bir kullanıcı arayüzüne neden oluyordu.

  1. Tespit: React DevTools Profiler, birçok bileşenin aynı anda ve sık sık render edildiğini gösterdi. Özellikle useContext kullanan bileşenlerin tamamı, Context değerindeki herhangi bir değişiklikte yeniden render oluyordu.
  2. Çözüm: Uygulama state'i üç ana Context'e bölündü: UserPreferencesContext, NotificationsContext ve ProjectDataContext. Her Context kendi reducer'ı ile yönetildi.
  3. Ek Optimizasyon: Her Context tüketicisi, use-context-selector kütüphanesi ile optimize edildi. Örneğin, bir "Tema Seçici" bileşeni sadece UserPreferencesContext içindeki theme özelliğini seçerken, bir "Bildirim Listesi" bileşeni sadece NotificationsContext içindeki unreadCount özelliğini seçti.

Bu değişiklikler sayesinde, kullanıcı temayı değiştirdiğinde sadece temayla ilgili bileşenler yeniden render edildi; analiz verileri güncellendiğinde sadece ilgili veri widget'ları yenilendi. Bu, dashboard'un genel tepki süresini önemli ölçüde artırdı ve kullanıcı deneyimini iyileştirdi.

Daha Akıllı Veri Yönetimi ile Render Sayısını Nasıl Azaltırız?

React uygulamalarında render performansını etkileyen önemli bir faktör de veri yönetimi ve veri çekme stratejileridir. Sunucudan veri çekme, önbellekleme ve kullanıcı etkileşimlerini yönetme şekliniz, uygulamanızın ne sıklıkta ve ne kadar verimli bir şekilde yeniden render edileceğini belirler.

Veri Çekme Stratejileri: TanStack Query (React Query) ve SWR gibi Kütüphanelerin Rolü Nedir?

Manuel

fetch

veya

axios

ile veri çekmek, basit senaryolar için yeterli olsa da, modern web uygulamaları için bir dizi zorluğu beraberinde getirir: önbellekleme, arka plan güncellemeleri, eş zamanlı istek yönetimi, hata yönetimi ve yeniden denemeler gibi. Bu zorluklar, kod karmaşıklığına ve potansiyel performans sorunlarına yol açabilir.

TanStack Query (eski adıyla React Query) ve SWR gibi kütüphaneler, bu sorunları çözmek için tasarlanmış güçlü veri çekme ve önbellekleme katmanları sunar. Temel faydaları şunlardır:

  • Otomatik Önbellekleme: Çekilen veriler otomatik olarak önbelleğe alınır. Bu, aynı veriye tekrar ihtiyaç duyulduğunda ağ isteği yapmadan doğrudan önbellekten servis edilmesini sağlar, böylece veri çekme süresini azaltır ve UI'ın daha hızlı görünmesini sağlar.
  • Arka Plan Güncelleme: Veri eskimiş olsa bile, kullanıcıya önbelleğe alınmış veriyi gösterirken arka planda yeni veriyi çekebilirler. Yeni veri geldiğinde UI sorunsuz bir şekilde güncellenir.
  • Odağa Dönüşte Yeniden Çekme (Refetch on Focus): Tarayıcı penceresine geri dönüldüğünde veya uygulama odağa geldiğinde veriyi otomatik olarak güncelleyerek her zaman güncel verinin gösterilmesini sağlar.
  • Hata Yönetimi ve Yeniden Denemeler: Yerleşik hata yönetimi ve özelleştirilebilir yeniden deneme stratejileri sunarlar, bu da uygulamanızı daha sağlam hale getirir.
  • Render Optimizasyonu: Bu kütüphaneler, yalnızca veri değiştiğinde veya bileşenlerin gerçekten ihtiyaç duyduğu zaman render'ı tetikleyerek gereksiz render'ları önemli ölçüde azaltır. Örneğin, bir bileşen aynı query key ile veri çekmek istediğinde, kütüphane önbelleğe alınmış veriyi kullanır ve bileşenin yeniden render edilmesini engeller.

import { useQuery } from '@tanstack/react-query';

const fetchUrunler = async () => {
  const res = await fetch('/api/urunler');
  if (!res.ok) {
    throw new Error('Veri çekilemedi');
  }
  return res.json();
};

const UrunListesi = () => {
  const { data: urunler, isLoading, isError } = useQuery({
    queryKey: ['urunler'], // Benzersiz anahtar
    queryFn: fetchUrunler,
    staleTime: 5 * 60 * 1000, // 5 dakika boyunca "eski" olarak kabul etme
  });

  if (isLoading) return 
Yükleniyor...
; if (isError) return
Hata oluştu!
; return (
    {urunler.map(urun => (
  • {urun.ad}
  • ))}
); };

Bu yaklaşım, hem geliştirici deneyimini iyileştirir hem de uygulamanın veri çekme ve güncelleme performansını otomatik olarak optimize eder.

Debouncing ve Throttling Teknikleri ile Kullanıcı Girişlerini Nasıl Yönetiriz?

Kullanıcı etkileşimleri, özellikle arama kutuları, kaydırma olayları veya pencere boyutlandırma gibi sık tetiklenen olaylar, sürekli olarak state güncellemelerine ve dolayısıyla render'lara neden olabilir. Debouncing ve Throttling, bu tür olayların tetiklenme sıklığını sınırlamak için kullanılan klasik optimizasyon teknikleridir.

  • Debouncing: Belirli bir olayın (örneğin, klavye girişi) yalnızca belirli bir süre (örneğin, 300ms) içinde başka bir olay tetiklenmediyse yürütülmesini sağlar. Kullanıcı yazmayı bıraktıktan sonra otomatik tamamlama önerilerini göstermek için idealdir.
    
    import React, { useState, useEffect } from 'react';
    import { debounce } from 'lodash';
    
    const AramaKutusu = () => {
      const [aramaTerimi, setAramaTerimi] = useState('');
    
      // Debounce edilmiş arama fonksiyonu
      const debouncedSearch = debounce((term) => {
        console.log('API çağrısı yapılıyor:', term);
        // API çağrısı veya state güncelleme burada
      }, 500);
    
      const handleChange = (e) => {
        const term = e.target.value;
        setAramaTerimi(term);
        debouncedSearch(term);
      };
    
      // Bileşen ayrıldığında debounce işlemini temizle
      useEffect(() => {
        return () => {
          debouncedSearch.cancel();
        };
      }, [debouncedSearch]);
    
      return (
        
      );
    };
                

  • Throttling: Bir olayın belirli bir zaman aralığı (örneğin, her 200ms) içinde en fazla bir kez tetiklenmesini sağlar. Pencere boyutlandırma veya kaydırma olaylarını yönetmek için kullanışlıdır, bu sayede olay işleyicisi sürekli olarak tetiklenmez.
    
    import React, { useState, useEffect } from 'react';
    import { throttle } from 'lodash';
    
    const PencereBoyutuGosterici = () => {
      const [boyut, setBoyut] = useState({ width: window.innerWidth, height: window.innerHeight });
    
      const handleResize = throttle(() => {
        setBoyut({ width: window.innerWidth, height: window.innerHeight });
      }, 200); // Her 200ms'de bir güncelle
    
      useEffect(() => {
        window.addEventListener('resize', handleResize);
        return () => {
          window.removeEventListener('resize', handleResize);
          handleResize.cancel(); // Temizleme
        };
      }, [handleResize]);
    
      return (
        

    Pencere Boyutu: {boyut.width}x{boyut.height}

    ); };

Bu teknikler, aşırı olay tetiklenmelerini kontrol altına alarak React bileşenlerinin gereksiz yere render edilmesini engeller ve CPU döngülerinden tasarruf sağlar.

Sanallaştırma (Virtualization) ve Pencereleme (Windowing) ile Büyük Listeleri Nasıl Optimizasyon Edersiniz?

Binlerce elemandan oluşan uzun listeler veya tablolar render etmek, DOM üzerinde ciddi performans sorunlarına yol açabilir. Tarayıcı, görünür olmasa bile her bir elemanın layout hesaplamalarını ve boyama işlemlerini yapmak zorundadır. Sanallaştırma (Virtualization) veya pencereleme (Windowing), bu sorunu çözmek için tasarlanmış bir optimizasyon tekniğidir.

Bu teknik, yalnızca o anda kullanıcının görünüm alanında (viewport) olan elemanları render eder. Kullanıcı kaydırdıkça, görünüm alanına giren yeni elemanlar render edilir ve görünüm alanından çıkan elemanlar DOM'dan kaldırılır. Bu sayede, DOM boyutu küçük tutulur ve render maliyeti önemli ölçüde düşer.

Popüler kütüphaneler arasında react-window ve react-virtualized bulunur. React-window, react-virtualized'ın daha hafif ve modern bir alternatifidir.


// react-window örneği
import React from 'react';
import { FixedSizeList } from 'react-window';

const Row = ({ index, style }) => (
  
Liste Öğesi {index}
); const BuyukListe = () => ( {Row} ); export default BuyukListe;

Yukarıdaki örnekte, 10.000 öğelik bir liste olmasına rağmen,

FixedSizeList

yalnızca o anda ekranda görünen öğeleri render eder. Bu, özellikle sosyal medya akışları, veri tabloları veya sohbet uygulamaları gibi büyük veri setleri olan uygulamalarda devasa bir performans artışı sağlar.

Üretim Ortamında React Performansını İzleme ve İyileştirme Kalıpları Nelerdir?

Uygulamanızı geliştirme ortamında optimize etmek bir başlangıçtır, ancak gerçek dünya performansını sağlamak için üretim ortamındaki davranışını izlemek ve iyileştirmek esastır. Kullanıcıların farklı cihazlarda, ağ koşullarında ve coğrafi konumlarda uygulamanızı nasıl deneyimlediğini anlamak kritik öneme sahiptir.

Bundle Boyutu Optimizasyonu: Code Splitting ve Lazy Loading ile Yükleme Sürelerini Nasıl Kısaltırız?

Modern JavaScript uygulamaları genellikle tek bir büyük JavaScript dosyasına (bundle) derlenir. Bu bundle'ın boyutu arttıkça, tarayıcının indirme, ayrıştırma ve çalıştırma süresi uzar, bu da ilk yükleme süresini (First Contentful Paint, FCP) ve etkileşim süresini (Time To Interactive, TTI) olumsuz etkiler.

  • Code Splitting (Kod Bölme): Uygulamanızın JavaScript kodunu daha küçük, yönetilebilir parçalara ayırma işlemidir. Bu parçalar, yalnızca ihtiyaç duyulduğunda (örneğin, kullanıcı belirli bir sayfayı ziyaret ettiğinde veya belirli bir bileşeni tıkladığında) indirilir.
  • Lazy Loading (Tembel Yükleme): Code splitting ile el ele gider. React'te
    React.lazy()

    ve

    Suspense

    ile tembel yükleme kolayca uygulanabilir. Bu, belirli bileşenlerin veya rotaların yalnızca gerektiğinde yüklenmesini sağlar.


    import React, { Suspense, lazy } from 'react';

    // Bileşeni tembel yükle
    const HakkindaSayfasi = lazy(() => import('./HakkindaSayfasi'));
    const IletisimSayfasi = lazy(() => import('./IletisimSayfasi'));

    const App = () => (


    Yükleniyor...

    }>

    } />
    } />
    } />


);

Bu örnekte,

HakkindaSayfasi

ve

IletisimSayfasi

bileşenleri, kullanıcı ilgili rotalara gidene kadar yüklenmeyecektir. Bu, uygulamanın ilk yükleme süresini önemli ölçüde azaltır.

Webpack veya Rollup gibi bundler'lar, otomatik olarak kod bölme yapabilir.

create-react-app

gibi araçlar da bu optimizasyonları varsayılan olarak destekler.

Performans Bütçeleri Belirleme ve Gerçek Kullanıcı İzleme (RUM) Araçları

Performansı sürekli olarak iyileştirmek için, ölçülebilir hedefler belirlemek önemlidir. "Performans Bütçeleri", uygulamanızın belirli metrikler (örneğin, JavaScript bundle boyutu, ilk boyama süresi, etkileşim süresi) için kabul edilebilir eşikleri tanımlamanızı sağlar. Bu bütçeleri aşan her durum, bir uyarı veya hata olarak ele alınabilir, böylece performans sorunları üretim ortamına ulaşmadan tespit edilebilir.

Gerçek Kullanıcı İzleme (Real User Monitoring - RUM): Geliştirme ortamında her senaryoyu test etmek imkansızdır. RUM araçları, gerçek kullanıcıların uygulamanızla nasıl etkileşime girdiğini izler ve performans metriklerini (örneğin, sayfa yükleme süreleri, FCP, LCP, CLS gibi Core Web Vitals metrikleri) toplar. Bu veriler, uygulamanızın gerçek dünya koşullarındaki performansını anlamak ve darboğazları tespit etmek için paha biçilmezdir.

Popüler RUM ve performans izleme araçları:

  • Google Lighthouse: Uygulamanızın performans, erişilebilirlik, SEO gibi alanlardaki puanlarını gösteren bir denetim aracıdır. Geliştirme sırasında ve CI/CD pipeline'larında kullanılabilir.
  • Google Core Web Vitals: Google'ın kullanıcı deneyimini ölçmek için kullandığı bir dizi metrik (Largest Contentful Paint, Cumulative Layout Shift, First Input Delay). Bunları izlemek, SEO performansınızı da etkileyebilir.
  • Sentry: Hata izleme aracı olmasının yanı sıra, performans izleme yetenekleri de sunar. Kullanıcıların yaşadığı yavaşlıkları ve performans sorunlarını gerçek zamanlı olarak görmenizi sağlar.
  • New Relic, Datadog, Dynatrace: Büyük ölçekli kurumsal uygulamalar için daha kapsamlı RUM ve APM (Application Performance Monitoring) çözümleri sunar.

Mobil Uyumluluk ve Duyarlı Tasarım İpuçları: Media Query Örnekleri

React uygulamalarının çoğu, mobil cihazlarda da erişilebilir olmalıdır. Mobil performans optimizasyonu, sadece JavaScript'i hızlandırmakla kalmaz, aynı zamanda duyarlı tasarıma ve mobil cihazların kısıtlı kaynaklarına (işlem gücü, batarya ömrü) uygun UI/UX'e de odaklanır. Duyarlı tasarım, farklı ekran boyutlarına ve cihazlara uyum sağlayan bir kullanıcı arayüzü oluşturmak anlamına gelir.

Media Queries (Medya Sorguları): CSS içinde belirli koşullara (ekran genişliği, cihaz tipi, yönlendirme vb.) göre stil kurallarını uygulamanızı sağlar. Bu, mobil cihazlar için farklı düzenler, yazı tipleri veya görüntü boyutları tanımlamanıza olanak tanır.


/* Genel stil */
.container {
  display: flex;
  flex-direction: row;
  padding: 20px;
}

.card {
  width: 30%;
  margin: 10px;
}

/* 768px genişliğinden küçük ekranlar için (tablet ve mobil) */
@media (max-width: 768px) {
  .container {
    flex-direction: column; /* Mobil cihazlarda sütun düzenine geç */
    padding: 10px;
  }

  .card {
    width: 90%; /* Mobil cihazlarda kartları tam genişlik yap */
    margin: 10px auto; /* Ortala */
  }

  h2 {
    font-size: 1.5rem; /* Başlık boyutunu küçült */
  }
}

/* 480px genişliğinden küçük ekranlar için (küçük mobil cihazlar) */
@media (max-width: 480px) {
  .card {
    padding: 10px;
  }

  img {
    max-width: 100%; /* Görüntülerin ekranı taşmasını engelle */
    height: auto;
  }
}
    

Mobil cihazlar için önemli diğer performans ipuçları:

  • Görüntü Optimizasyonu: Doğru boyutlarda ve modern formatlarda (WebP, AVIF) görüntüler kullanın.
    lazy-loading

    özelliği ile görüntülerin sadece görünüm alanına girdiğinde yüklenmesini sağlayın.

  • Dokunmatik Hedef Boyutları: Küçük düğmeler ve bağlantılar mobil cihazlarda kötü bir kullanıcı deneyimine yol açar. Dokunmatik hedeflerin yeterince büyük olduğundan emin olun.
  • Mobil Uyumlu Fontlar: Mobil cihazlarda iyi okunabilen ve hızlı yüklenen yazı tipleri kullanın.
  • Gereksiz Animasyonları Azaltın: Karmaşık veya yoğun animasyonlar mobil cihazlarda yavaşlamaya neden olabilir. Performans dostu animasyonlar kullanın veya mobil cihazlarda daha basit versiyonlarını gösterin.

Bu kalıpları ve araçları kullanarak, React uygulamanızın sadece geliştirme sırasında değil, üretim ortamında da hızlı, duyarlı ve kullanıcı dostu olmasını sağlayabilirsiniz.

React Render Optimizasyonu Hakkında Sıkça Sorulan Sorular

React render optimizasyonu karmaşık bir konu olabilir, ancak doğru yaklaşımlar ve araçlarla uygulamalarınızın performansını önemli ölçüde artırmak mümkündür. İşte sıkça karşılaşılan bazı sorular ve cevapları:

1. Her bileşenimi React.memo ile sarmalamalı mıyım?

Hayır, kesinlikle her bileşeni

React.memo

ile sarmalamamalısınız.

React.memo

kullanmanın kendisi de bir maliyet içerir (props karşılaştırma işlemi). Eğer bir bileşen çok sık değişen props'lara sahipse, veya render maliyeti zaten çok düşükse,

React.memo

kullanmak gereksiz yere ek yük oluşturabilir. Genellikle, büyük ve karmaşık alt ağaçları olan, sık değişmeyen props'lara sahip ve üst bileşenleri sık sık render olan bileşenler için en faydalıdır. Profiler araçlarıyla darboğazları tespit ettikten sonra hedefli optimizasyon yapmak en iyi yaklaşımdır.

2. useMemo ve useCallback ne zaman kullanılmalı?

useMemo

, bir fonksiyonun döndürdüğü değeri (örneğin, bir dizi, bir nesne veya pahalı bir hesaplamanın sonucu) önbelleğe almak için kullanılır. Bağımlılıkları değişmediği sürece bu değeri yeniden hesaplamaz.

useCallback

ise, bir fonksiyonun referansını önbelleğe almak için kullanılır. Genellikle, bir fonksiyonu

React.memo

ile sarmalanmış alt bileşenlere prop olarak geçirirken gereksiz yeniden render'ları önlemek için kullanılır. Her ikisi de, yalnızca referans eşitliğinin önemli olduğu durumlarda (genellikle diğer memoization teknikleriyle birlikte) veya pahalı hesaplamaları önbelleğe alırken kullanılmalıdır.

3. Context API kullanmak her zaman performansı düşürür mü?

Context API'nın değeri değiştiğinde, onu tüketen tüm bileşenler yeniden render edilir. Bu durum, özellikle tek bir büyük Context kullanarak çok sayıda state'i paylaşıyorsanız, gereksiz render'lara yol açarak performansı düşürebilir. Ancak bu, Context API'dan kaçınmanız gerektiği anlamına gelmez. Stratejik olarak Context'leri daha küçük, mantıksal parçalara ayırmak veya

use-context-selector

gibi kütüphanelerle selektörler kullanarak sadece ihtiyaç duyulan state parçaları değiştiğinde render'ı tetiklemek, performans sorunlarını büyük ölçüde hafifletebilir. Doğru kullanıldığında, Context API harika bir state yönetim çözümüdür.

4. React'te render optimizasyonu için en iyi ilk adım nedir?

En iyi ilk adım her zaman uygulamayı profillemektir. React DevTools Profiler'ı kullanarak hangi bileşenlerin ne sıklıkta ve ne kadar sürede render edildiğini tespit edin. Performans darboğazlarını doğru bir şekilde teşhis etmeden yapılan optimizasyonlar genellikle etkisizdir veya hatta daha kötü sonuçlar doğurabilir. "Neden yeniden render edildi?" sorusunu sormak ve

why-did-you-render

gibi araçlarla bu sorunun cevabını bulmak, optimizasyon yolculuğunuzda size doğru yönü gösterecektir.

5. Sadece memoization teknikleri ile tüm performans sorunlarını çözebilir miyim?

Hayır, memoization önemli bir araçtır ancak tüm performans sorunlarını tek başına çözemez. Performans optimizasyonu bütüncül bir yaklaşımdır. Memoization'ın yanı sıra, etkili veri çekme stratejileri (TanStack Query gibi), büyük listeler için sanallaştırma, bundle boyutu optimizasyonu (code splitting), debouncing/throttling gibi teknikler ve üretim ortamında gerçek kullanıcı performansını izlemek (RUM araçları) de kritik öneme sahiptir. Optimal performans için bu farklı katmanlarda da optimizasyon yapılması gerekir.

Sonuç olarak, React render optimizasyonu, iyi bir kullanıcı deneyimi sunmak ve uygulamanızın uzun vadede ölçeklenebilir olmasını sağlamak için vazgeçilmez bir beceridir. Bu rehberde öğrendiğiniz teknikleri ve araçları kullanarak, React uygulamalarınızı daha hızlı, daha verimli ve daha keyifli hale getirebilirsiniz. Unutmayın, optimizasyon bir süreçtir, bir varış noktası değil!

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.