aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Web Geliştirme

React Performans Optimizasyonu: memo, useMemo, useCallback

React performans sorunlarının büyük çoğunluğu kötü algoritmadan değil, gereksiz render'lardan kaynaklanır. Bir bileşen her render olduğunda alt ağacını da yeniden çalıştırır; çoğu zaman bu ucuzdur, ama büyük listeler, ağır hesaplamalar veya derin ağaçlar söz konusu olduğunda arayüz takılmaya başlar. Bu yazıda render'ları nasıl ölçeceğini ve memo, useMemo, useCallback üçlüsüyle nasıl makul biçimde azaltacağını anlatıyorum.

React neden yeniden render eder?

Bir bileşen üç durumda yeniden render olur: kendi state'i değiştiğinde, aldığı props değiştiğinde veya üst bileşeni render olduğunda. Son madde en sık gözden kaçan noktadır: ebeveyn render olunca, props'u hiç değişmemiş olsa bile çocuklar varsayılan olarak yeniden render edilir.

Çoğu durumda bu bir sorun değildir; React'in render'ı saf JavaScript yürütmesidir ve gerçek DOM'a ancak fark varsa dokunur. Sorun, render'ın kendisi pahalı olduğunda başlar: binlerce satırlık tablo, karmaşık grafik, ya da her render'da yeniden hesaplanan ağır bir döngü.

Optimize etmeden önce ölç

Erken optimizasyon en büyük tuzaktır. memo'yu her yere serpiştirmeden önce nerede yavaşladığını ölç. React Developer Tools eklentisindeki Profiler sekmesi tam bunun içindir: kaydı başlat, etkileşimi yap, durdur. Hangi bileşenin kaç kez ve kaç milisaniye render olduğunu alev grafiğinde görürsün.

Geliştirme aşamasında hangi bileşenlerin gereksiz render olduğunu görmek için Profiler'ın "Record why each component rendered" seçeneğini aç. Render sebebinin "Parent component rendered" olduğunu sık görüyorsan, memoizasyon adayı bulmuşsun demektir.

React.memo ile bileşeni hatırlatmak

React.memo, bir bileşeni sarar ve props'u sığ (shallow) karşılaştırma ile değişmediyse render'ı atlar. Saf, props'a bağlı sunum bileşenleri için idealdir.

import { memo } from 'react';

const UserRow = memo(function UserRow({ name, email }) {
  console.log('render', name);
  return (
    <tr>
      <td>{name}</td>
      <td>{email}</td>
    </tr>
  );
});

Artık UserRow, name veya email değişmediği sürece ebeveyn render olsa bile yeniden render olmaz. Ama dikkat: sığ karşılaştırma, nesne ve fonksiyon prop'larını referansla kıyaslar. Her render'da yeni bir nesne/fonksiyon üretirsen, memo hiçbir işe yaramaz çünkü referans her seferinde değişir.

useMemo ile pahalı hesaplamaları önbelleğe almak

useMemo bir değeri hatırlar: verdiğin bağımlılıklar değişmedikçe hesaplamayı yeniden çalıştırmaz. İki temel kullanımı vardır: gerçekten pahalı hesaplamaları atlamak ve memo'lu çocuklara verilecek nesne/dizi referansını sabit tutmak.

const sortedUsers = useMemo(() => {
  return [...users].sort((a, b) => a.name.localeCompare(b.name));
}, [users]);

Burada sıralama yalnızca users dizisi değiştiğinde çalışır; ebeveynin başka bir state'i değişip render tetiklediğinde sıralama atlanır. Önemli kural: bağımlılık dizisini dürüst tut. Hesaplama içinde kullandığın her dış değişken dizide olmalı, yoksa bayat (stale) değerlerle çalışırsın.

Her şeyi useMemo'ya sarma cazibesine kapılma. Karşılaştırma ve önbelleğin kendisi de bir maliyettir; a + b gibi ucuz bir hesabı memoize etmek net zarardır.

useCallback ile fonksiyon referansını sabitlemek

useCallback aslında useMemo'nun fonksiyona özel halidir: bir fonksiyonun referansını bağımlılıklar değişene kadar korur. Asıl değeri, memo'lu bir çocuğa callback geçirdiğinde ortaya çıkar.

const handleDelete = useCallback((id) => {
  setUsers((prev) => prev.filter((u) => u.id !== id));
}, []);

// memo'lu çocuk artık bu prop yüzünden boşuna render olmaz
<UserRow user={user} onDelete={handleDelete} />

Bağımlılık dizisi boşsa fonksiyon ilk render'da bir kez oluşturulur ve hep aynı referans kalır. setUsers'ın güncelleyici (functional) biçimini kullandığıma dikkat et — böylece users'ı bağımlılığa eklemek zorunda kalmıyorum ve callback'i sabit tutuyorum.

Sık yapılan hatalar

  • memo'lu bileşene yeni nesne literali geçirmek: style={{ margin: 8 }} her render'da yeni referanstır ve memoizasyonu boşa çıkarır. Sabit nesneyi bileşen dışına taşı veya useMemo ile üret.
  • Liste anahtarı olarak index kullanmak: Sıralama veya ekleme/silme olan listelerde key={index} yanlış eşleşmelere ve gereksiz render'lara yol açar; kararlı bir id kullan.
  • Her yere optimizasyon serpmek: Ölçmeden eklenen memo/useMemo kod okunabilirliğini düşürür, fayda sağlamaz.

React 19 ve derleyici notu

React 19 ile gelen React Compiler, bu memoizasyonların büyük kısmını derleme zamanında otomatik yapmayı hedefler; yani gelecekte elle useMemo/useCallback yazma ihtiyacı azalabilir. Yine de derleyici her projede henüz devrede değil ve mevcut kod tabanlarında bu kavramları anlamak hâlâ şart. Önce ölç, sonra düşün, en son optimize et prensibi her sürümde geçerli.

Sık Sorulan Sorular

memo, useMemo ve useCallback arasındaki fark nedir?

memo bir bileşeni sarar ve props değişmedikçe render'ını atlar. useMemo bir değeri önbelleğe alır. useCallback ise bir fonksiyon referansını sabit tutar; aslında useMemo(() => fn, deps)'in kısayoludur.

Her bileşeni memo ile sarmalı mıyım?

Hayır. Karşılaştırmanın da bir maliyeti var ve ucuz render'larda net zarar olur. Yalnızca Profiler ile gerçekten pahalı veya çok sık render olduğunu kanıtladığın bileşenleri memoize et.

useMemo gerçekten daha hızlı yapar mı?

Sadece hesaplama yeterince pahalıysa. Basit aritmetik veya küçük dizilerde önbelleğin maliyeti faydadan büyük olabilir. Ölçüp karar ver.

React uygulamanı hızlandırmak mı istiyorsun? Render profilini çıkarıp darboğazları gerçek ölçümlerle gidermek için benimle iletişime geç.

Bu kategorideki tüm yazılar →

Devamı için