React DevTools Profiler ile Gereksiz Render'ı Bulmak
React DevTools Profiler'ın flame graph, commit timeline ve 'Why did this render?' panelini gerçek bir tanı sürecinde nasıl okuyacağınızı öğrenin; gereksiz render'ı bileşen düzeyinde tespit edin.
Bir bileşen ağacı büyüdükçe "neden bu kadar yavaş?" sorusunun cevabı gözle görülmez hale gelir. Animasyon kekeler, form alanına yazarken her tuş gecikmeli yansır, sayfa geçişleri tahmin ettiğinizden uzun sürer. Sorunun kaynağı genellikle tek bir bileşen değil, görünmez bir render zinciridir; her güncelleme onlarca alt bileşeni tetikler ve bu maliyet birikerek kullanıcıya dokunur.
React DevTools Profiler bu zinciri kayıt altına alır ve görünür kılar. Hangi bileşenin ne kadar sürede render olduğunu, kaç commit üretildiğini ve her render'ın neden başlatıldığını milisaniye düzeyinde gösterir. Aracın doğru okunması birkaç saatlik tahmin oyununu kısa bir analiz seansına indirebilir.
Profiler'ın üç temel görünümü (flame graph, commit timeline ve "Why did this render?" paneli) gerçek bir tanı sürecinde nasıl okunur, aşağıda o sırayla işleniyor. Kurulum adımları değil; bulgu okuma ve yorumlama.
Profiler'a başvurmadan önce: belirtiyi netleştirmek
Profiler bir kayıt açar ve siz ne yaparsanız onu yakalar. Kayıt başlatmadan önce hangi etkileşimin yavaş olduğunu bilmiyorsanız yüzlerce commit görürsünüz ve hangisinin sorunlu olduğunu yine bilemezsiniz. Önce belirtiyi tanımlayın.
"Genel yavaşlık" iyi bir başlangıç noktası değildir. "Arama kutusuna yazarken her tuş basışından sonra liste gecikmeli güncelleniyor" daha net bir hedeftir. Belirtiyi o kadar daraltın ki kayıt sırasında yalnızca o etkileşimi tekrarlayın ve hemen durdurun. Analiz edeceğiniz commit sayısı küçük kalır, gürültü azalır.
Profiler geliştirme modunda açıldığında render süreleri, production derlemesine kıyasla belirgin biçimde yüksek ölçülür; React geliştirme modunda ek kontroller çalıştırır. Production ortamını profillemek istiyorsanız uygulamanızı react-dom/profiling paketi ile derlemeniz gerekir. Karşılaştırmalı ölçüm yapacaksanız iki kaydın da aynı modda alındığına dikkat edin, aksi halde rakamlar yanıltıcı olur.
Flame graph: renk ve genişlik neyi söyler?
Flame graph, tek bir commit içindeki bileşen ağacını görselleştirir. Yatay eksen zaman değil ağaç derinliğidir; dikey eksen ise ebeveyn-çocuk ilişkisini yansıtır. Her dikdörtgen bir bileşeni temsil eder.
Renk bilgi taşır. Gri bileşen o commit'te render olmamıştır; memoize edilmiş ya da aldığı props değişmemiştir. Sarı veya turuncu bileşen render olmuştur ve renk ne kadar koyuysa render o kadar uzun sürmüştür. Dikdörtgenin genişliği ise o bileşenin commit içindeki toplam maliyetini, kendi render süresi ve tüm çocuklarının süresi dahil, gösterir.
Doğru soru şudur. Tanıya başlamak için en geniş ve en koyu dikdörtgeni aramak mantıklı görünür; ama daha verimli soru "neden bu dikdörtgen gri değil?" sorusudur. Gri olmayan her bileşen için render'ın gerçekten gerekli olup olmadığını sorgulayın. Çoğu gereksiz render sorunu, güncellenmemesi gereken bir bileşenin neden render aldığını yanıtlamaktan ortaya çıkar.
Geniş ama soluk bir dikdörtgen maliyetin çocuklardan geldiğine işaret eder. Bu durumda üst bileşeni değil, koyu renkli çocukları inceleyin. Yanlış bileşene odaklanmak hem zaman kaybettirir hem de asıl sorunu görünmez kılar.
Commit timeline: kaç commit, hangi commit?
Profiler'ın üst kısmındaki commit timeline, kayıt boyunca gerçekleşen her commit'i bir çubuk olarak listeler. Çubuğun yüksekliği o commit'in toplam render süresini temsil eder. Tüm timeline tek bakışta anomaliyi ortaya koyar: bir çubuk diğerlerinden belirgin biçimde uzunsa ya da beklediğinizden fazla commit oluşmuşsa, orası inceleme noktasıdır.
Commit sayısı beklentinizden fazlaysa, tek bir kullanıcı eylemine karşılık birden fazla ayrı state güncellemesi yapılıyordur. React 18'in otomatik batching özelliği event handler içindeki setState çağrılarını tek bir commit'te birleştirir, ama async işlemler veya promise zinciri içinde çağrılan setState'ler her güncellemede ayrı commit üretebilir. Timeline'da bunu art arda gelen küçük çubuklar olarak görürsünüz.
Bir form alanına her karakter girişinde tek bir commit oluşuyor ama commit çok uzunsa, state yanlış seviyede tutuluyor olabilir. Form state'i kök bileşende yaşıyorsa her tuş basışı kökten aşağı tüm ağacı tetikler ve flame graph size geniş, koyu bir kök dikdörtgeni gösterir. Çözüm genellikle state'i o veriyi gerçekten kullanan bileşene yaklaştırmaktır; kök bileşenin neyi bilmesi gerektiğini sormak bu kararı somutlaştırır.
"Rendered by" paneli: render zincirini geriye doğru izlemek
Flame graph'ta bir bileşene tıkladığınızda sağ panelde "Rendered by" listesi açılır. Bu liste render'ı başlatan üst bileşenleri gösterir ve sorumluluğun nerede olduğunu anlamanızı sağlar.
Render zinciri her zaman düşündüğünüz yerde başlamaz. Bir liste satırı gereksiz render alıyorsa ve flame graph'ta belirginleşiyorsa, "Rendered by" listesinde Context Provider ya da global state bağlantısı görünebilir. Context güncellemesi tüm tüketicileri yeniden render eder; bunu beklemediyseniz bu panel sizi doğru yere götürür. Context API'nin tüketici zincirleme davranışını anlamak, Provider yapısını ne zaman bölmek gerektiğine karar vermede temel bir girdi olur.
"Rendered by" zincirini yukarı doğru izlerken hangi bileşenin gerçekten sorumlu olduğuna dikkat edin. Bazen zincirin başındaki bileşen yalnızca tetikleyicidir; asıl sorun onun props yapısındadır. Bir üst bileşen her render'da yeni bir obje literal oluşturup bunu alt bileşene geçiriyorsa, referans her seferinde değişir ve memo gibi koruyucular etkisiz kalır.
"Why did this render?" paneli: nedeni bileşen düzeyinde okumak
Profiler ayarlarından "Record why each component rendered while profiling" seçeneğini etkinleştirdiğinizde, her bileşen için render'ın neden başlatıldığını görebilirsiniz. Panel dört kategori üretebilir: props değişti, state değişti, hooks değişti, parent render aldı.
En sık karşılaşılan mesaj "parent rendered"dır. Bu mesaj tek başına sorun değildir; ebeveyn render alınca çocukların da render alması beklenen davranıştır. Sorun, çocuğun aldığı props'ta gerçek bir fark olmamasına rağmen zincirin devam etmesidir. Bu durumu gördüğünüzde iki soru sorun: bu bileşen memo ile korunabilir mi, ve korunursa gerçekten fark yaratır mı?
"Props changed" mesajı geldiğinde hangi prop'un değiştiğini görmek için bileşene tıklayın; Profiler değişen prop'u ismiyle listeler. Çoğu zaman değişen içerik değil referanstır. Obje veya dizi props olarak geçiliyorsa ve her render'da yeniden oluşturuluyorsa referans değişimi kaçınılmazdır. useMemo ve useCallback'in referans kararlılığı sorunundaki gerçek katkısını değerlendirmek için önce bu "props changed" kaydını elinizde bulundurmanız gerekir; aksi halde optimizasyonun işe yarayıp yaramadığını ölçemezsiniz.
"Hooks changed" mesajı genellikle useContext, useState veya özel hook çıktısının değiştiğini gösterir. Bir custom hook içinde her render'da yeni bir obje döndürülüyorsa ve bunu kullanan bileşenler değişiklik algılıyorsa, hook'un döndürdüğü referansı kararlı hale getirmek öncelikli adımdır.
Commit sayısı neden beklentiyi aşar?
Commit sayısı tek bir etkileşim için beklediğinizin iki ya da üç katıysa, render döngüsü yaşanıyor olabilir. Bir bileşen render alır, bu render bir state güncellemesi tetikler, güncelleme yeni bir render başlatır; zincir devam eder.
Döngü. Profiler'da art arda gelen commit'ler olarak görünür ve her biri aynı bileşen kümesini içerir. Timeline'da ritimli bir örüntü dikkat çeker. Bu durumda "Why did this render?" panelinde state değişimini kimin başlattığına bakın, ardından o state'i güncelleyen kodu izleyin.
useEffect bağımlılık listesinin yanlış kurulması bu döngünün yaygın kaynaklarından biridir. Effect her render'dan sonra çalışır, bağımlılıkta bir obje referansı varsa ve bu referans her render'da yeniliyorsa, effect yeniden çalışır, state güncellenir, yeni render başlatılır. Profiler bu nedenselliği doğrudan göstermez; ama yüksek commit sayısını ve tekrar eden bileşen kümesini işaret eder. Hangi effect'in sorumlu olduğunu bulmak, commit içeriğini bağımlılık koduna bağlamayı gerektirir.
Profiler kaydı memo'nun gerçekten kesip kesmediğini gösterir
React.memo bir bileşeni sarmak render sorununu her zaman çözmez. Bileşen flame graph'ta hâlâ koyu renkte görünüyorsa, memo eklenmiş ama render durdurulamıyorsa, nedeni şunlardan biri olabilir: props referansı hâlâ değişiyor, bileşen useContext kullanıyor ve Context güncellemesi memo'yu bypass ediyor ya da karşılaştırma işlevi yanlış yazılmış.
Profiler bu farkı somut biçimde gösterir: memo gerçekten çalıştığında bileşen flame graph'ta gridir. Hâlâ renkli görünüyorsa memo etkisizdir. React.memo'nun hangi koşullarda render'ı engellediğini anlamak bu ayrımı netleştirir; profiler kaydı ise tartışmayı somut bir gözleme dönüştürür: çalışıyor mu, çalışmıyor mu?
Gereksiz render her zaman performans sorunu değildir. Milisaniyenin altında tamamlanan, DOM değişikliği üretmeyen bir render çoğu durumda kullanıcı tarafından fark edilmez. Şu soruyu sorun: frame düşüyor mu, input gecikmeli yanıt veriyor mu, kullanıcı bir şeyi hissediyor mu? Cevap hayırsa profiler kaydını kapatıp başka şeye geçmek doğru karardır. Optimizasyon maliyeti, kazancın olmadığı yerde artıya geçmez.
Profiler'ın ölçemediği şeyler
Profiler render maliyetini ölçer ama tüm performans sorunlarını yakalamaz. Ağ gecikmesi, büyük JavaScript ayrıştırma (parse) süresi, uzun görevler (long tasks) veya layout hesaplama maliyeti profiler kaydında görünmez. Bir bileşen hızlı render alıyor ama verisi geç geliyorsa, Profiler size sorunsuz bir grafik gösterir; gerçek gecikme başka bir araçla bulunur.
Profiler geliştirici makinesinde çalıştığından ortalama bir mobil cihazın CPU kısıtlamasını da yansıtmaz. Profiler analizi darboğazı bulmaya yardım eder, ama o darboğazın kullanıcı deneyimine ne kadar yansıdığını söylemez. Yayına çıkmadan önce performans kontrolünü daha geniş bir çerçeveye oturtmak, profiler bulgularını gerçek koşullarla ilişkilendirmeye yardım eder.
Çok bileşenli uygulamalarda render maliyetini düşürmek yalnızca profiler bulgularına dayanan tek seferlik bir işlem değildir; bileşen mimarisi, state yerleşimi ve veri akışı kararları bu maliyetin temelini oluşturur. Profiler bu kararların sonuçlarını ölçer, nedenlerini değil.
Profiler gerçek sorunları bulmayı kolaylaştırır ama bir kaydı açıp bakmanın kendisi sonuç üretmez. Belirtiyi önceden netleştirmek, kayıt süresini kısa tutmak ve her commit'te "bu render gerekli miydi?" sorusunu sormak analizin verimliliğini belirler. Gri bileşen zaten iyi çalışıyor demektir; enerjinizi koyu renkli ve beklenmedik yerlerdeki bileşenlere harcayın.
Flame graph render maliyetini görünür kılar, commit timeline onu zaman içinde konumlandırır, "Why did this render?" paneli sorumluluğu isimlendirir. Üçü bir arada kullanıldığında tahmin oyunu yerini kanıta bırakır ve düzeltme kararı çok daha temelli olur.