Merhaba arkadaşlar, bugün React'te özellikle büyük uygulamalarda başımı çok ağrıtan bir performans sorunundan ve onun en temiz çözümünden bahsedeceğim. Child component'lere fonksiyon prop'u geçtiğimizde yaşanan gereksiz re-render'lar beni deli etmişti! Özellikle listelemelerde veya formlarda, her tuşa basışta veya mouse hareketinde tüm ağacın yeniden render edilmesi performansı mahvediyordu.
İşin özü şu: React, fonksiyonları referans bazlı karşılaştırır. Normal bir fonksiyon tanımı, her render'da yeni bir referans oluşturur. Bu da React.memo kullansanız bile child component'inizin "prop'lar değişti" diye yeniden render olmasına sebep olur.
Sorunun Canlı Örneği
Aşağıdaki gibi bir yapınız olduğunu düşünün. Her input değiştiğinde, sadece MyInput değil, Button component'i de gereksiz yere render olacaktır!
Çözüm: useCallback Hook'u
İşte sihirli kancamız: useCallback. Bu hook, fonksiyonunuzu bir dependency array'e (bağımlılık dizisi) bağlı olarak önbelleğe alır (memoize eder). Eğer bağımlılıklar değişmemişse, fonksiyon aynı referansı korur ve child component gereksiz render'dan kurtulur.
Ne Zaman Kullanmalı? & Uyarılar
Her fonksiyonu useCallback yapmayın! Bu bir optimizasyon aracıdır. Sadece aşağıdaki durumlarda kullanmak mantıklıdır:
1. React.memo ile sarmalanmış bir child component'e prop geçiyorsanız.
2. Fonksiyonunuz, useEffect gibi bir hook'un dependency array'inde kullanılıyorsa.
3. Çok sık render olan büyük listelerde (virtual scroll) callback'leriniz varsa.
En kritik nokta: Dependency array'i ([] veya [count]) doğru doldurmak. İçinde kullandığınız tüm state ve prop'ları eklemezseniz, fonksiyonunuz eski (stale) değerleri kullanır ve bug'a sebep olur.
Sonuç olarak, useCallback ile gereksiz render'ları önlemek, uygulamanızın performansını ve kullanıcı deneyimini gözle görülür şekilde iyileştirir. Ama her aracı olduğu yerde kullanmak, kod karmaşıklığını arttırmaktan başka işe yaramaz.
Peki siz bu sorunla nasıl başa çıkıyorsunuz? useCallback kullanırken başınıza gelen ilginç bug'lar oldu mu? Yoksa useMemo ile başka türlü bir optimizasyon mu yapıyorsunuz? Yorumlarda deneyimlerinizi paylaşın!
İşin özü şu: React, fonksiyonları referans bazlı karşılaştırır. Normal bir fonksiyon tanımı, her render'da yeni bir referans oluşturur. Bu da React.memo kullansanız bile child component'inizin "prop'lar değişti" diye yeniden render olmasına sebep olur.
Aşağıdaki gibi bir yapınız olduğunu düşünün. Her input değiştiğinde, sadece MyInput değil, Button component'i de gereksiz yere render olacaktır!
JavaScript:
import React, { useState } from 'react';
const Button = React.memo(({ onClick, children }) => {
console.log('Button rendered!'); // Her tıklamada bu logu göreceksiniz!
return <button onClick={onClick}>{children}</button>;
});
function MyInput() {
const [value, setValue] = useState('');
// PROBLEM BURADA! Her render'da yeni bir fonksiyon oluşuyor.
const handleClick = () => {
alert('Butona tıklandı!');
};
return (
<div>
<input value={value} onChange={(e) => setValue(e.target.value)} />
<Button onClick={handleClick}>Tıkla Bana</Button>
</div>
);
}
İşte sihirli kancamız: useCallback. Bu hook, fonksiyonunuzu bir dependency array'e (bağımlılık dizisi) bağlı olarak önbelleğe alır (memoize eder). Eğer bağımlılıklar değişmemişse, fonksiyon aynı referansı korur ve child component gereksiz render'dan kurtulur.
JavaScript:
import React, { useState, useCallback } from 'react';
const Button = React.memo(({ onClick, children }) => {
console.log('Button rendered!'); // Sadece gerçekten gerekli olduğunda render olacak!
return <button onClick={onClick}>{children}</button>;
});
function MyInput() {
const [value, setValue] = useState('');
const [count, setCount] = useState(0);
// ÇÖZÜM BURADA! Fonksiyon referansı count değişmediği sürece sabit kalır.
const handleClick = useCallback(() => {
alert(`Butona tıklandı! Count: ${count}`);
}, [count]); // count değişirse, fonksiyon yeniden oluşturulur.
return (
<div>
<input value={value} onChange={(e) => setValue(e.target.value)} />
<Button onClick={handleClick}>Tıkla Bana (Count: {count})</Button>
<button onClick={() => setCount(c => c + 1)}>Count'u Arttır</button>
</div>
);
}
Her fonksiyonu useCallback yapmayın! Bu bir optimizasyon aracıdır. Sadece aşağıdaki durumlarda kullanmak mantıklıdır:
1. React.memo ile sarmalanmış bir child component'e prop geçiyorsanız.
2. Fonksiyonunuz, useEffect gibi bir hook'un dependency array'inde kullanılıyorsa.
3. Çok sık render olan büyük listelerde (virtual scroll) callback'leriniz varsa.
En kritik nokta: Dependency array'i ([] veya [count]) doğru doldurmak. İçinde kullandığınız tüm state ve prop'ları eklemezseniz, fonksiyonunuz eski (stale) değerleri kullanır ve bug'a sebep olur.
Sonuç olarak, useCallback ile gereksiz render'ları önlemek, uygulamanızın performansını ve kullanıcı deneyimini gözle görülür şekilde iyileştirir. Ama her aracı olduğu yerde kullanmak, kod karmaşıklığını arttırmaktan başka işe yaramaz.
Peki siz bu sorunla nasıl başa çıkıyorsunuz? useCallback kullanırken başınıza gelen ilginç bug'lar oldu mu? Yoksa useMemo ile başka türlü bir optimizasyon mu yapıyorsunuz? Yorumlarda deneyimlerinizi paylaşın!