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.
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.
- 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.
- 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.
- Çözüm Önerisi: Bu durumda,
React.memokullanarak "Ü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}
{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.
- Tespit: React DevTools Profiler, birçok bileşenin aynı anda ve sık sık render edildiğini gösterdi. Özellikle
useContextkullanan bileşenlerin tamamı, Context değerindeki herhangi bir değişiklikte yeniden render oluyordu. - Çözüm: Uygulama state'i üç ana Context'e bölündü:
UserPreferencesContext,NotificationsContextveProjectDataContext. Her Context kendi reducer'ı ile yönetildi. - Ek Optimizasyon: Her Context tüketicisi,
use-context-selectorkütüphanesi ile optimize edildi. Örneğin, bir "Tema Seçici" bileşeni sadeceUserPreferencesContextiçindekithemeözelliğini seçerken, bir "Bildirim Listesi" bileşeni sadeceNotificationsContextiçindekiunreadCountö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
Suspenseile 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... }>
} />
} />
} />