useMemo ve useCallback: Performans Optimizasyonu Rehberi

useMemo ve useCallback: Performans Optimizasyonu Rehberi
React bileşenleri her render'da fonksiyon gövdesini baştan çalıştırır. Bu, hesaplama maliyetli işlemlerin ve fonksiyon referanslarının gereksiz yere yeniden oluşturulması anlamına gelir. useMemo ve useCallback hook'ları, bu durumları optimize etmenizi sağlayan memoization araçlarıdır. Bu rehberde her iki hook'un doğru kullanımını, ne zaman kullanılması gerektiğini ve yaygın yanlış kullanımları inceleyeceğiz.
Memoization Nedir?
Memoization, bir fonksiyonun sonucunu önbelleğe alarak aynı girdilerle tekrar çağrıldığında hesaplamayı tekrarlamamasını sağlayan optimizasyon tekniğidir. React'te bu kavram iki hook ile uygulanır:
- useMemo: Hesaplanmış bir değeri memoize eder
- useCallback: Bir fonksiyon referansını memoize eder
useCallback(fn, deps)aslındauseMemo(() => fn, deps)ile eşdeğerdir. useCallback, fonksiyon memoizasyonu için daha okunabilir bir kısayoldur.
useMemo: Hesaplanmış Değerleri Önbelleğe Alma
import { useMemo, useState } from 'react';
function ProductFilter({ products }) {
const [search, setSearch] = useState('');
const [sortBy, setSortBy] = useState('name');
const [minPrice, setMinPrice] = useState(0);
// Maliyetli hesaplama - useMemo ile optimize edilmiş
const filteredAndSortedProducts = useMemo(() => {
console.log('Filtreleme ve sıralama çalışıyor...');
return products
.filter(product =>
product.name.toLowerCase().includes(search.toLowerCase()) &&
product.price >= minPrice
)
.sort((a, b) => {
if (sortBy === 'name') return a.name.localeCompare(b.name, 'tr');
if (sortBy === 'price') return a.price - b.price;
if (sortBy === 'rating') return b.rating - a.rating;
return 0;
});
}, [products, search, sortBy, minPrice]);
// Derived values - useMemo ile
const stats = useMemo(() => ({
total: filteredAndSortedProducts.length,
avgPrice: filteredAndSortedProducts.length > 0
? filteredAndSortedProducts.reduce((sum, p) => sum + p.price, 0) / filteredAndSortedProducts.length
: 0,
maxPrice: Math.max(...filteredAndSortedProducts.map(p => p.price), 0)
}), [filteredAndSortedProducts]);
return (
<div>
<input value={search} onChange={(e) => setSearch(e.target.value)} placeholder="Ara..." />
<select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
<option value="name">Ada Göre</option>
<option value="price">Fiyata Göre</option>
<option value="rating">Puana Göre</option>
</select>
<p>{stats.total} ürün | Ort. Fiyat: {stats.avgPrice.toFixed(2)} TL</p>
{filteredAndSortedProducts.map(product => (
<div key={product.id}>{product.name} - {product.price} TL</div>
))}
</div>
);
}
useCallback: Fonksiyon Referanslarını Koruma
useCallback, bir fonksiyonun referansını bağımlılıklar değişmediği sürece sabit tutar. Bu özellikle React.memo ile sarılmış alt bileşenlere fonksiyon prop'u geçerken önemlidir:
import { useCallback, useState, memo } from 'react';
// React.memo ile sarılmış alt bileşen
const TodoItem = memo(function TodoItem({ todo, onToggle, onDelete }) {
console.log(`TodoItem render: ${todo.text}`);
return (
<li>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>Sil</button>
</li>
);
});
function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
// useCallback ile fonksiyon referansı korunur
const handleToggle = useCallback((id) => {
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
)
);
}, []);
const handleDelete = useCallback((id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
}, []);
const handleAdd = useCallback(() => {
if (!input.trim()) return;
setTodos(prev => [...prev, { id: Date.now(), text: input, done: false }]);
setInput('');
}, [input]);
return (
<div>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={handleAdd}>Ekle</button>
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={handleToggle}
onDelete={handleDelete}
/>
))}
</ul>
</div>
);
}
İpucu: useCallback, React.memo ile birlikte kullanılmadığında genellikle fayda sağlamaz. Alt bileşen memo ile sarılmamışsa, useCallback gereksiz bir overhead yaratır. Önce profiling yapın, sonra optimize edin.
Ne Zaman Kullanmalı, Ne Zaman Kullanmamalı?
| Kullanın | Kullanmayın |
|---|---|
| Büyük liste filtreleme/sıralama | Basit hesaplamalar (a + b) |
| Karmaşık matematiksel işlemler | Primitive değer oluşturma |
| React.memo ile birlikte callback aktarma | Memo olmadan callback aktarma |
| useEffect dependency olarak kullanılan değerler | Her fonksiyon ve değeri sarmak |
| Custom hook'lardan döndürülen fonksiyonlar | Erken optimizasyon |
function Example() {
const [count, setCount] = useState(0);
// GEREKSIZ useMemo - basit hesaplama
const doubled = useMemo(() => count * 2, [count]); // Buna gerek yok
const doubled = count * 2; // Bu yeterli
// GEREKSIZ useCallback - memo olmayan bileşene aktarma
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []); // Alt bileşen memo değilse faydasız
// FAYDALI useMemo - pahalı hesaplama
const expensiveResult = useMemo(() => {
return heavyComputation(largeDataSet);
}, [largeDataSet]);
return <div>{/* ... */}</div>;
}
Uyarı: useMemo ve useCallback'in kendileri de bir maliyet taşır: dependency karşılaştırması, bellek kullanımı ve kod karmaşıklığı. Her yerde kullanmak, hiç kullanmamaktan daha kötü olabilir. React Profiler ile gerçek performans sorunlarını tespit edip, yalnızca ihtiyaç duyulan yerlerde kullanın.
React.memo ile Birlikte Kullanım
// Pahalı render'a sahip bileşen
const ExpensiveChart = memo(function ExpensiveChart({ data, onDataPointClick }) {
// Karmaşık grafik render işlemi
return <canvas>{/* chart */}</canvas>;
});
function Dashboard() {
const [rawData, setRawData] = useState([]);
const [selectedRange, setSelectedRange] = useState('week');
// useMemo: data her render'da yeni referans olmasın
const chartData = useMemo(() => {
return processDataForChart(rawData, selectedRange);
}, [rawData, selectedRange]);
// useCallback: fonksiyon referansı değişmesin
const handleDataPointClick = useCallback((point) => {
console.log('Veri noktası:', point);
}, []);
return (
<div>
{/* Bu bileşen yalnızca chartData veya handleDataPointClick değiştiğinde render olur */}
<ExpensiveChart data={chartData} onDataPointClick={handleDataPointClick} />
</div>
);
}
Sonuç
useMemo ve useCallback, React performans optimizasyonunun önemli araçlarıdır, ancak her derde deva değildir. "Ölç, sonra optimize et" prensibini takip edin. React DevTools Profiler ile gerçek darboğazları tespit edin ve yalnızca anlamlı iyileştirme sağlayacak yerlerde bu hook'ları kullanın. React.memo ile birlikte kullanıldığında gereksiz re-render'ları etkili bir şekilde önleyebilirsiniz.