Merhaba arkadaşlar, bugün başımı çok ağrıtan bir sorunu ve nasıl çözdüğümü anlatacağım. Projemdeki bir sayfa, kullanıcı herhangi bir işlem yapmadığı halde sürekli ve gereksiz yere render oluyordu. Performans gözle görülür şekilde düşmüştü, özellikle mobilde. "Bu hatayı ilk gördüğümde kafayı yemiştim" diyebilirim. Neyse ki, React DevTools'un Profiler sekmesi imdadıma yetişti ve bugün size bu süreci nasıl yönettiğimi anlatacağım.
Sorunun Keşfi: Profiler'ı Açıyoruz
İlk adım, Chrome'da React DevTools'u açıp Profiler sekmesine geçmek. Burada mavi bir "Record" düğmesi var. Bu düğmeye basıp, sorunlu olduğunu düşündüğümüz işlemleri (örneğin sayfada gezme, bir butona tıklama) yaptıktan sonra tekrar "Stop" diyoruz. Kayıt bittiğinde, her bir component'ın ne zaman ve neden render edildiğini gösteren bir zaman çizelgesi karşımıza çıkıyor.
Render Nedenlerini Okumak
Profiler, bir component'ın neden render olduğunu bize söylüyor. En yaygın sebepler şunlar: Props değişti, State değişti, Parent component yeniden render oldu. Benim durumumda, sorunlu component'ın yanında "did not update" yazısını görmeyi umuyordum ama ne yazık ki sürekli "Why did this render?" butonu aktifti. Bu butona tıkladığımda, karşıma çıkan detay beni şaşırttı.
Gördüğünüz gibi, `handleClick` fonksiyonu component'ın içinde tanımlandığı için, her render'da yeni bir referans değeri alıyordu. Bu da `onClick` prop'unun sürekli değiştiği anlamına geliyordu ve alt component (`ListItem`) her seferinde gereksiz yere yeniden render oluyordu.
Çözüm: useCallback ve React.memo
İşte benim kullandığım en temiz çözüm. İlk olarak, fonksiyonu useCallback hook'u ile memoize ediyoruz. Böylece, bağımlılık dizisi (`[]`) değişmediği sürece fonksiyon aynı referansı koruyor.
Bu iki düzeltmeyi yaptıktan sonra Profiler'ı tekrar çalıştırdığımda, artık `MemoizedListItem` component'ının yanında gurur verici şekilde "did not update" yazdığını gördüm. Performans grafiği anında düzeldi!
Siz de React projelerinizde benzer performans sıkıntıları yaşadınız mı? Özellikle büyük listeler veya karmaşık formlarda bu tarz gereksiz render'lar başınızı ağrıtıyor mu? Sizin kullandığınız farklı bir optimizasyon yöntemi veya favori performans izleme aracınız var mı? Yorumlarda paylaşalım, hep birlikte öğrenelim.
İlk adım, Chrome'da React DevTools'u açıp Profiler sekmesine geçmek. Burada mavi bir "Record" düğmesi var. Bu düğmeye basıp, sorunlu olduğunu düşündüğümüz işlemleri (örneğin sayfada gezme, bir butona tıklama) yaptıktan sonra tekrar "Stop" diyoruz. Kayıt bittiğinde, her bir component'ın ne zaman ve neden render edildiğini gösteren bir zaman çizelgesi karşımıza çıkıyor.
Profiler, bir component'ın neden render olduğunu bize söylüyor. En yaygın sebepler şunlar: Props değişti, State değişti, Parent component yeniden render oldu. Benim durumumda, sorunlu component'ın yanında "did not update" yazısını görmeyi umuyordum ama ne yazık ki sürekli "Why did this render?" butonu aktifti. Bu butona tıkladığımda, karşıma çıkan detay beni şaşırttı.
JavaScript:
// Profiler bana şunu gösterdi:
// "Props changed: `onClick`"
// Ama bu fonksiyon, her render'da yeniden oluşturuluyordu!
function SorunluListe({ items }) {
const handleClick = (itemId) => {
console.log('Tıklandı:', itemId);
};
return (
<ul>
{items.map(item => (
<ListItem
key={item.id}
item={item}
onClick={handleClick} // BU PROP HER RENDER'DA YENİ BİR FONKSİYON!
/>
))}
</ul>
);
}
Gördüğünüz gibi, `handleClick` fonksiyonu component'ın içinde tanımlandığı için, her render'da yeni bir referans değeri alıyordu. Bu da `onClick` prop'unun sürekli değiştiği anlamına geliyordu ve alt component (`ListItem`) her seferinde gereksiz yere yeniden render oluyordu.
İşte benim kullandığım en temiz çözüm. İlk olarak, fonksiyonu useCallback hook'u ile memoize ediyoruz. Böylece, bağımlılık dizisi (`[]`) değişmediği sürece fonksiyon aynı referansı koruyor.
JavaScript:
import React, { useCallback } from 'react';
function DuzeltilmisListe({ items }) {
// Fonksiyon artık render'lar arasında sabit kalıyor.
const handleClick = useCallback((itemId) => {
console.log('Tıklandı:', itemId);
}, []); // Bağımlılık yok, sadece bir kere oluşturulur.
return (
<ul>
{items.map(item => (
<MemoizedListItem
key={item.id}
item={item}
onClick={handleClick} // Artık aynı referans!
/>
))}
</ul>
);
}
// İkinci adım: Alt component'ı React.memo ile sarmalayarak,
// prop'ları değişmediği sürece yeniden render olmasını engelliyoruz.
const MemoizedListItem = React.memo(function ListItem({ item, onClick }) {
console.log('ListItem Render Edildi:', item.id); // Artık sadece gerektiğinde loglanacak!
return (
<li onClick={() => onClick(item.id)}>
{item.name}
</li>
);
});
Bu iki düzeltmeyi yaptıktan sonra Profiler'ı tekrar çalıştırdığımda, artık `MemoizedListItem` component'ının yanında gurur verici şekilde "did not update" yazdığını gördüm. Performans grafiği anında düzeldi!
Siz de React projelerinizde benzer performans sıkıntıları yaşadınız mı? Özellikle büyük listeler veya karmaşık formlarda bu tarz gereksiz render'lar başınızı ağrıtıyor mu? Sizin kullandığınız farklı bir optimizasyon yöntemi veya favori performans izleme aracınız var mı? Yorumlarda paylaşalım, hep birlikte öğrenelim.