React Performance Profiling: DevTools ile Analiz

React Performance Profiling: DevTools ile Analiz
React uygulamalarında performans sorunları genellikle gereksiz re-render'lar, büyük component tree'ler ve optimize edilmemiş state yönetiminden kaynaklanır. React DevTools Profiler, bu sorunları tespit etmek için güçlü bir araçtır. Bu rehberde React Profiler'ı etkili kullanmayı, yaygın performans sorunlarını teşhis etmeyi ve React.memo, useMemo, useCallback gibi optimizasyon tekniklerini doğru senaryolarda uygulamayı öğreneceksiniz.
React DevTools Profiler Kullanımı
React DevTools Profiler, her render döngüsünde hangi bileşenlerin render edildiğini, ne kadar sürdüğünü ve neden tetiklendiğini gösterir:
- React DevTools tarayıcı eklentisini kurun
- DevTools'ta "Profiler" sekmesine geçin
- Ayarlardan "Record why each component rendered" seçeneğini aktifleyin
- Mavi kayıt butonuna basın ve uygulamada etkileşim yapın
- Kaydı durdurup flame chart'ı inceleyin
Programatik Profiling
React'in yerleşik Profiler bileşeni ile render sürelerini programatik olarak ölçebilirsiniz:
import { Profiler, useState, useMemo } from 'react';
function onRenderCallback(
id, // Profiler tree'sinin id'si
phase, // "mount" veya "update"
actualDuration, // Bu render'ın süresi (ms)
baseDuration, // Memo olmadan tahmini süre
startTime, // Render başlangıç zamanı
commitTime // Commit zamanı
) {
// Yavaş render'ları logla
if (actualDuration > 16) { // 16ms = 60fps frame budget
console.warn(`Yavaş render: ${id}`, {
phase,
duration: `${actualDuration.toFixed(2)}ms`,
baseDuration: `${baseDuration.toFixed(2)}ms`
});
// Monitoring servisine gönder
sendToMonitoring({
component: id,
duration: actualDuration,
phase,
timestamp: commitTime
});
}
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<Profiler id="Header" onRender={onRenderCallback}>
<Header />
</Profiler>
<Profiler id="ProductList" onRender={onRenderCallback}>
<ProductList />
</Profiler>
</Profiler>
);
}
Gereksiz Re-render'ları Tespit Etme ve Önleme
React'te en yaygın performans sorunu gereksiz re-render'lardır. Bir parent bileşen render olduğunda tüm child bileşenler de render olur:
// SORUN: ExpensiveList, count her değiştiğinde gereksiz yere render olur
function App() {
const [count, setCount] = useState(0);
const [items] = useState(generateLargeList());
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<ExpensiveList items={items} /> {/* Her count değişiminde render! */}
</div>
);
}
// ÇÖZÜM 1: React.memo ile child bileşeni sarma
const ExpensiveList = React.memo(function ExpensiveList({ items }) {
console.log('ExpensiveList rendered'); // Sadece items değiştiğinde
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
});
// ÇÖZÜM 2: Composition pattern ile state'i izole etme
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}
function App() {
const [items] = useState(generateLargeList());
return (
<div>
<Counter /> {/* State burada izole */}
<ExpensiveList items={items} /> {/* Etkilenmez */}
</div>
);
}
React.memo'yu her yerde kullanmayın. Memo'nun kendisi de maliyet taşır (prop karşılaştırma). Sadece gerçekten yavaş render eden veya sık gereksiz render olan bileşenlerde kullanın. Profiler ile önce sorunu doğrulayın, sonra optimize edin.
useMemo ve useCallback Doğru Kullanımı
Bu hook'lar referans kararlılığı sağlayarak gereksiz hesaplama ve re-render'ları önler:
function ProductPage({ products, category }) {
// useMemo: Pahalı hesaplamayı cache'le
// Sadece products veya category değiştiğinde yeniden hesapla
const filteredProducts = useMemo(() => {
console.log('Filtering products...'); // Ne sıklıkta çağrıldığını izle
return products
.filter(p => p.category === category)
.sort((a, b) => b.rating - a.rating);
}, [products, category]);
// useCallback: Fonksiyon referansını kararlı tut
// React.memo'lu child bileşene prop olarak geçirirken gerekli
const handleAddToCart = useCallback((productId) => {
addToCart(productId);
showNotification('Ürün sepete eklendi');
}, []); // Dış bağımlılık yoksa boş array
return (
<div>
<h2>{category} ({filteredProducts.length} ürün)</h2>
{filteredProducts.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={handleAddToCart} // Kararlı referans
/>
))}
</div>
);
}
// React.memo ile useCallback birlikte etkili
const ProductCard = React.memo(function ProductCard({ product, onAddToCart }) {
return (
<div className="product-card">
<h3>{product.name}</h3>
<p>{product.price} TL</p>
<button onClick={() => onAddToCart(product.id)}>Sepete Ekle</button>
</div>
);
});
Virtualization ile Büyük Listeler
Binlerce öğe içeren listelerde virtualization kullanarak sadece görünen öğeleri render edin:
// react-window ile virtualized list
import { FixedSizeList as List } from 'react-window';
function VirtualizedProductList({ products }) {
const Row = ({ index, style }) => (
<div style={style} className="product-row">
<span>{products[index].name}</span>
<span>{products[index].price} TL</span>
</div>
);
return (
<List
height={600} // Container yüksekliği
itemCount={products.length}
itemSize={50} // Her satır yüksekliği
width="100%"
overscanCount={5} // Viewport dışında fazladan render edilecek satır
>
{Row}
</List>
);
}
// 10.000 ürün: Normal render ~500ms, Virtualized ~5ms
useTransition hook'u, düşük öncelikli state güncellemelerini işaretleyerek UI'ın responsive kalmasını sağlar. Arama filtreleme gibi yoğun render işlemleri için startTransition kullanarak kullanıcı girişinin bloke olmasını önleyebilirsiniz.
React 18+ Concurrent Features
import { useState, useTransition, useDeferredValue } from 'react';
function SearchProducts() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
// useDeferredValue: Arama sonuçlarını geciktir
const deferredQuery = useDeferredValue(query);
return (
<div>
<input
value={query}
onChange={(e) => {
// Input güncellemesi yüksek öncelikli (anında)
setQuery(e.target.value);
}}
placeholder="Ürün ara..."
/>
{isPending && <span>Aranıyor...</span>}
{/* Liste düşük öncelikli render - UI donmaz */}
<ProductList query={deferredQuery} />
</div>
);
}
Sonuç
React performans optimizasyonu, önce ölçüm sonra iyileştirme prensibine dayanır. React DevTools Profiler ile sorunlu bileşenleri tespit edin, gereksiz re-render'ları composition pattern veya React.memo ile önleyin, pahalı hesaplamaları useMemo ile cache'leyin ve büyük listelerde virtualization kullanın. Erken optimizasyondan kaçının; Profiler'ın gösterdiği gerçek darboğazlara odaklanın.