React Render Optimizasyonu: Gereksiz Render'ları Önleme

React Render Optimizasyonu: Gereksiz Render'ları Önleme
React uygulamalarında performans sorunlarının en yaygın kaynağı gereksiz render'lardır. Bir component, props veya state değişmediği halde yeniden render oluyorsa, CPU kaynakları boşa harcanır ve kullanıcı deneyimi bozulur. Bu rehberde, gereksiz render'ların nedenlerini tespit etmeyi ve React.memo, useMemo, useCallback gibi araçlarla nasıl önleyeceğinizi adım adım öğreneceksiniz.
React Neden Yeniden Render Eder?
React'te bir component şu durumlarda yeniden render olur: state değiştiğinde, parent component render olduğunda (props değişmese bile), consume ettiği context değiştiğinde. Bu kuralları anlamak, optimizasyonun ilk adımıdır.
React DevTools Profiler ile Sorun Tespiti
Optimizasyon yapmadan önce, hangi componentlerin gereksiz render olduğunu tespit etmeniz gerekir. React DevTools Profiler bu iş için en etkili araçtır. Profiler sekmesinde "Highlight updates when components render" seçeneğini aktif ederek, render olan componentleri görsel olarak takip edebilirsiniz.
import React, { useState, memo, useMemo, useCallback } from 'react';
// ❌ KÖTÜ: Her render'da yeni object referansı oluşur
function BadParent() {
const [count, setCount] = useState(0);
// Her render'da yeni bir object oluşur
const style = { color: 'red', fontSize: 16 };
// Her render'da yeni bir fonksiyon oluşur
const handleClick = () => console.log('clicked');
// Her render'da yeni bir array oluşur
const items = [1, 2, 3];
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
{/* count değiştiğinde ChildComponent da render olur! */}
<ChildComponent style={style} onClick={handleClick} items={items} />
</div>
);
}
// ✅ İYİ: Referansları memoize et
function GoodParent() {
const [count, setCount] = useState(0);
const style = useMemo(() => ({ color: 'red', fontSize: 16 }), []);
const handleClick = useCallback(() => console.log('clicked'), []);
const items = useMemo(() => [1, 2, 3], []);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<ChildComponent style={style} onClick={handleClick} items={items} />
</div>
);
}
// React.memo ile sarılmış child
const ChildComponent = memo(function ChildComponent({
style,
onClick,
items,
}: {
style: React.CSSProperties;
onClick: () => void;
items: number[];
}) {
console.log('ChildComponent rendered');
return (
<div style={style} onClick={onClick}>
{items.map((item) => (
<span key={item}>{item}</span>
))}
</div>
);
});
Büyük Listelerde Virtualization
Binlerce öğe içeren listelerde, sadece ekranda görünen öğeleri render etmek dramatik bir performans artışı sağlar. react-window veya @tanstack/react-virtual gibi kütüphaneler bu işi kolaylaştırır.
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
interface VirtualListProps {
items: string[];
}
function VirtualList({ items }: VirtualListProps) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50, // Her satırın tahmini yüksekliği (px)
overscan: 5, // Görünür alanın dışında kaç öğe render edilecek
});
return (
<div ref={parentRef} style={{ height: '500px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
<div className="border-b py-3 px-4">
{items[virtualItem.index]}
</div>
</div>
))}
</div>
</div>
);
}
// 100.000 öğelik liste bile akıcı şekilde scroll edilebilir
function App() {
const items = Array.from({ length: 100000 }, (_, i) => `Öğe ${i + 1}`);
return <VirtualList items={items} />;
}
State Yapısını Optimize Etme
State'in doğru yapılandırılması, gereksiz render'ları önlemenin en temel yoludur. State'i mümkün olduğunca kullanıldığı yere yakın tutmak (colocation) ve büyük state'leri parçalara ayırmak önemlidir.
// ❌ KÖTÜ: Tüm form state'i tek bir object'te
function BadForm() {
const [formState, setFormState] = useState({
name: '',
email: '',
address: '',
phone: '',
preferences: { theme: 'light', notifications: true },
});
// name değiştiğinde tüm form yeniden render olur!
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormState(prev => ({ ...prev, name: e.target.value }));
};
return <form>{/* ... */}</form>;
}
// ✅ İYİ: State'i parçala ve ihtiyaç duyan componentlere taşı
function GoodForm() {
return (
<form>
<NameField />
<EmailField />
<AddressField />
<PhoneField />
<PreferencesSection />
</form>
);
}
// Her alan kendi state'ini yönetir
function NameField() {
const [name, setName] = useState('');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Adınız"
/>
);
}
Children Pattern ile Optimizasyon
Sık güncellenen bir parent component'in children'larının gereksiz render olmasını önlemek için children pattern kullanabilirsiniz. Bu teknik, React'in composition modelinin gücünü ortaya koyar.
Sonuç
React render optimizasyonu, "önce ölç, sonra optimize et" prensibine dayanmalıdır. React DevTools Profiler ile sorunlu alanları tespit edin, React.memo ile gereksiz render'ları önleyin, useMemo ve useCallback ile referans kararlılığını sağlayın ve büyük listeler için virtualization kullanın. Ancak erken optimizasyondan kaçının — her useMemo bir karmaşıklık ekler. Performans sorununu kanıtlayın, sonra optimize edin.