İçeriğe geç
Zeynep Baş — Ana sayfa
Tüm yazılar
  • React

React'te useMemo, useCallback, useRef ve useQuery: Hangisini Ne Zaman Kullanmalıyım?

Bir hook'u kullanmadan önce hangi problemi çözdüğünü anlamak: render, referans, hesaplama ve API çağrıları üzerinden pratik bir rehber.

Zeynep Baş5 dk okuma

Önce Hook'u Değil, Problemi Tanımla

React'te performans optimizasyonunun ilk kuralı

React'te bir problem gördüğümüzde akla hemen useMemo veya useCallback gelmesi kolay. Ancak doğru yaklaşım önce problemin ne olduğunu anlamak. Gereksiz bir hesaplama mı var? Bir child component gereksiz render mı oluyor? Render tetiklemeden bir değeri mi saklamak gerekiyor? Yoksa aslında problem React render'ı değil, gereksiz API çağrıları mı?

Bu ayrım önemli çünkü useMemo, useCallback ve useRef aynı problemi çözmez. useQuery ise zaten farklı bir kategoridedir: server state ve API verisinin yönetimiyle ilgilenir.

useMemo: Hesaplanan Değeri Ne Zaman Hatırlamalıyım?

Amaç render'ı durdurmak değil, hesaplamayı gereksiz yere tekrar etmemektir

useMemo bir değeri memoize eder. Örneğin elimizde büyük bir products listesi varsa ve search değerine göre filtreleme yapıyorsak, her parent render'ında aynı filtreleme işlemini tekrar yapmak gereksiz olabilir.

Örneğin favorites değiştiğinde component yeniden render olabilir. Ancak products ve search değişmediyse filtreleme sonucunun değişmesine gerek yoktur.

Burada önemli ayrım şu: useMemo component'in render olmasını engellemez. Parent yine render olabilir. Sadece memoize edilen hesaplama dependency'ler değişmediği sürece yeniden çalışmaz.

Bu nedenle küçük ve ucuz bir hesaplama için her şeyi useMemo ile sarmak doğru bir optimizasyon yaklaşımı değildir. Önce gerçekten tekrar hesaplamanın maliyetli olup olmadığına bakmak gerekir.

useCallback: Fonksiyonun Kendisi Değil, Referansı Önemli

Bir fonksiyon prop olarak geçildiğinde neden önemli hale geliyor?

useCallback bir fonksiyonun referansını sabit tutar. Özellikle fonksiyon bir child component'e prop olarak geçiliyor ve child React.memo ile memoize edilmişse anlamlı hale gelir.

Örneğin parent'taki favorites state'i değiştiğinde parent yeniden render olur. Normal bir fonksiyon her render'da yeni bir referans oluşturur. Bu fonksiyon memo'lanmış bir child'a prop olarak geçiliyorsa child'ın prop karşılaştırması başarısız olabilir.

Fakat fonksiyon aynı component içinde kullanılıyorsa ve child'a prop olarak geçilmiyorsa useCallback eklemek otomatik olarak performans kazancı sağlamaz. Örneğin aynı component'teki bir button'ın onClick handler'ı için sırf fonksiyon olduğu için useCallback kullanmak gerekli değildir.

React.memo, useMemo ve useCallback Birbirinin Yerine Geçmez

Üç kavramı aynı problem sanmamak gerekiyor

React.memo component seviyesinde çalışır. Bir component'in props'ları değişmediyse yeniden render edilmesini atlamasına yardımcı olabilir. useMemo ise bir hesaplamanın sonucunu, useCallback ise bir fonksiyon referansını memoize eder.

Örneğin ProductCard memo ile sarılmışsa ve parent'tan data ile onFavorite prop'ları geliyorsa, bu prop'ların referanslarının gereksiz yere değişmemesi önem kazanır.

Burada üç aracın da kullanılması zorunlu değildir. Önce child'ın gerçekten gereksiz render olup olmadığına bakılır. Sonra hangi prop'un referansının değiştiği incelenir.

useRef: Değişsin Ama Render Olmasın

useRef'i state'in alternatifi olarak düşünmemek gerekiyor

useRef render tetiklemeden bir değeri saklamak istediğimizde kullanılır. Ref'in current değeri değiştiğinde React yeniden render olmaz.

Bu nedenle useRef, örneğin DOM elementine erişmek, timer ID'si saklamak veya önceki bir değeri takip etmek gibi durumlarda kullanışlıdır.

Örneğin search input'unda debounce mekanizmasını manuel kurarken timer referansını useRef içinde tutmak mümkün olabilir. Buradaki amaç timer değiştiğinde component'i tekrar render ettirmemektir.

Ancak ekranda gösterilecek bir değer değiştiğinde useRef kullanmak genellikle doğru araç değildir. UI'ın güncellenmesi gerekiyorsa state gerekir.

API Aramalarında Problem useMemo Değil, Debounce Olabilir

Her input değişiminde API çağırmak başka bir problemdir

Bir search input'unun her karakterinde backend'e istek gönderiyorsak problem artık sadece render performansı değildir. Kullanıcı 'ali' yazarken a, al ve ali için ayrı API çağrıları yapılabilir.

Backend search destekliyorsa daha uygun bir akış genellikle input değerini debounce etmek ve debounce edilmiş değeri API sorgusunda kullanmaktır.

Burada useMemo API çağrısını azaltan bir araç değildir. useRef de tek başına debounce değildir. Debounce, kullanıcının yazmayı kısa süre bırakmasını bekleyerek gereksiz istekleri azaltan ayrı bir tekniktir.

useQuery: Client State Değil, Server State Problemi

API verisini yönetmek ile component state'ini yönetmek aynı şey değil

useQuery, TanStack Query'nin server state yönetimi için sunduğu araçlardan biridir. API'den veri çekme, loading ve error durumları, cache, refetch ve query yaşam döngüsü gibi konuları yönetir.

Örneğin backend search destekliyorsa search değerini queryKey'in parçası yapabiliriz.

Burada queryKey sadece bir isim değildir. Farklı search değerlerinin farklı server state'lerini temsil etmesini sağlar. Örneğin ['products', 'ali'] ile ['products', 'veli'] farklı sorgular olarak ele alınabilir ve cache'de ayrı tutulabilir.

Backend Search Desteklemiyorsa Ne Olur?

Bütün veriyi çekip frontend'de filtrelemek başka bir maliyet modeli yaratır

Backend search parametresi desteklemiyorsa frontend tüm ürünleri alıp filter ile arama yapabilir. Bu durumda useMemo, filtreleme hesabının gereksiz tekrarlarını azaltmak için düşünülebilir.

Örneğin products listesi büyükse ve search dışında favorites gibi başka state'ler de component'i yeniden render ediyorsa useMemo anlamlı olabilir.

Ancak useMemo ağ üzerinden taşınan veri miktarını azaltmaz. Binlerce kaydı frontend'e gönderen bir API'nin network ve backend maliyeti hâlâ vardır. Veri büyüdükçe server-side search, pagination ve uygun API tasarımı daha önemli hale gelir.

Peki Hangisini Ne Zaman Kullanmalıyım?

Hook ezberlemek yerine problemi eşleştir

Pahalı bir hesaplamanın sonucunu tekrar kullanmak istiyorsan useMemo'yu düşün. Bir fonksiyonun referansını özellikle memo'lanmış bir child'a karşı sabit tutman gerekiyorsa useCallback'i düşün. Bir değeri render tetiklemeden saklamak veya DOM/timer gibi mutable bir değere erişmek istiyorsan useRef'i düşün.

API'den gelen veriyi cache, loading, error ve refetch gibi server state ihtiyaçlarıyla yönetiyorsan useQuery'yi düşün. Kullanıcı input'u çok sık değiştiği için gereksiz API çağrıları oluşuyorsa debounce kullan. Backend search desteklemiyorsa frontend filtering yapılabilir; burada filtreleme maliyeti yükseliyorsa useMemo değerlendirilebilir.

Sonuç: Hook Değil, Problem Odaklı Düşün

React performansında en önemli beceri doğru aracı seçebilmek

React'te iyi performans optimizasyonu daha fazla hook kullanmak anlamına gelmez. Tam tersine, gereksiz abstraction ve memoization da kodu karmaşıklaştırabilir.

useMemo 'her hesaplamaya', useCallback 'her fonksiyona', useRef 'her değişkene' veya useQuery 'her veriye' eklenmez. Önce problemin kaynağı belirlenir: hesaplama mı pahalı, referans mı değişiyor, render olmadan değer mi saklanmalı, yoksa server state mi yönetiliyor?

Frontend'de önemli olan sadece kodun çalışması değil, neden o şekilde çalıştığını bilmektir. Bir hook'u ne zaman kullanmadığını açıklayabilmek de, onu kullanabilmek kadar önemli bir mühendislik becerisidir.

Diğer yazılar