İçeriğe geç
react

useMemo ve useCallback: Performans Optimizasyonu Rehberi

Tarık Tunç
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ında useMemo(() => 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.

Kaynaklar

İlgili Yazılar