Yazılarımız

OfisData

REACT RENDERİNG MEKANİZMASINI ANLAMAK VE GEREKSİZ RENDER AZALTMAK

Bir sayfada küçük bir filtre değiştiriyorsunuz, ama tüm liste yeniden çiziliyor; “hafif” dediğiniz bileşenler gözle görülür biçimde yavaşlıyor. React’te bu tür performans sorunlarının kökü çoğu zaman render mekanizmasını yanlış anlamaktan gelir. Render, “DOM’a yazmak” değildir; React’in ekranı nasıl güncelleyeceğine karar verdiği bir hesaplama sürecidir.

İyi haber şu: Gereksiz render’ları azaltmak için sihirli bir eklentiye ihtiyacınız yok. Ne zaman render tetikleniyor, React “değişti” kararını nasıl veriyor ve hangi optimizasyonlar neyi çözüyor; bunları netleştirince performans şikâyetlerinin büyük kısmı sistematik olarak çözülür.

Bu rehberde “reconciliation”, “commit”, “memoization” gibi kavramları sade bir çerçeveyle ele alıp, gerçekçi örneklerle ilerleyeceğiz. Uygulamalı alıştırmalar ve performans inceleme adımları için react eğitimi sayfasına da göz atabilirsiniz.

Geliştirici ekranında React DevTools Profiler açık, yanında render sürelerini gösteren zaman çizelgesi

Render sürecini doğru tanımlamak ve çerçevelemek

React’te “render”, bileşenin UI çıktısını üretme işidir. JSX’in fonksiyon çağrısı gibi çalıştığını düşünün: State/props değiştiğinde bileşen fonksiyonu tekrar çalışır, React de yeni UI ağacını hesaplar. Bu aşama, “ne gösterilmeli?” sorusunu cevaplar.

Sonra React, yeni ağaç ile eski ağaç arasındaki farkı bulur ve gerekli değişiklikleri uygular. Burada iki kritik kavram ayrılır: render phase ve commit phase. Render phase daha çok hesaplama ve karşılaştırma iken, commit phase ekrana yazma (DOM/Native) kısmıdır.

Render ve commit ayrımını netleştirerek konuşmak

Bir bileşen “render oldu” denildiğinde, her zaman DOM’da bir değişiklik olmuş olmayabilir. React, render phase’te yeni çıktıyı üretir; eğer gerçek bir fark yoksa commit daha hafif olur. Bu ayrımı bilmek, “neden render sayısı yüksek ama ekran aynı?” sorusunu açıklamayı sağlar.

Reconciliation mantığını basitleştirerek izlemek

Reconciliation, React’in “eski UI ağacı” ile “yeni UI ağacı” arasındaki farkı bulma sürecidir. Key’ler, bileşen kimliği ve props değişimleri burada belirleyicidir. Yanlış key kullanımı ya da gereksiz yeniden yaratılan bileşenler, diff maliyetini büyütür ve zincirleme güncellemeler doğurur.


Render tetikleyicilerini doğru tanımlamak ve izlemek

Gereksiz render azaltmanın ilk adımı, render’ı neyin tetiklediğini sınıflandırmaktır. React’te en yaygın tetikleyiciler: state güncellemesi, props değişimi, context güncellemesi ve parent bileşenin yeniden render olmasıdır.

State güncellemelerini kontrollü kullanmak ve sınırlandırmak

State değişince bileşen yeniden çalışır. Ancak state’i “gereken en küçük alanda” tutmak önemlidir. Çok üst seviyede tutulan state, geniş bir alt ağacı gereksiz yere hareketlendirir. Ayrıca aynı değer tekrar set edilse bile (özellikle obje/array referansı değişiyorsa) yeniden render tetiklenebilir.

Props referanslarını stabil tutmak ve sadeleştirmek

Props olarak geçirilen obje, array ya da inline fonksiyonlar her render’da yeni referans üretir. Child bileşen props “değişti” diye yeniden çalışır. Çözüm çoğu zaman props tasarımını sade tutmak ve referansları stabil kılmaktır (useMemo/useCallback gibi).

  • Inline object yerine memoize edilmiş obje ile ilerlemek
  • Inline function yerine useCallback ile fonksiyonu sabitlemek
  • Gereksiz props taşımayı bırakıp veri sınırı çizmek
  • Context değerini bölerek tüketimi küçültmek

Memoization yaklaşımını doğru seçmek ve uygulamak

Memoization, her şeyi otomatik hızlandırmaz; yanlış kullanılırsa kod karmaşasını artırır. Temel fikir, “aynı girdiler varsa aynı çıktıyı tekrar hesaplama”dır. React tarafında bunun üç yaygın aracı var: React.memo, useMemo ve useCallback.

React.memo kullanımını ölçerek yerleştirmek

React.memo, props eşitliği bozulmadıkça bileşenin yeniden render olmasını engelleyebilir. Ancak props olarak her seferinde yeni referanslar geçiyorsanız memo’nun faydası düşer. Önce tetikleyiciyi bulup, sonra memo ile kapatmak daha sağlıklı olur.

useMemo ve useCallback ile referansı sabitlemek

useMemo, pahalı hesaplamaları ya da prop olarak geçilecek objeleri sabitlemek için; useCallback ise fonksiyon referansını sabitlemek için kullanılır. Burada en önemli nokta dependency listesini doğru kurmaktır; aksi halde stale (eski) veriyle çalışmak gibi hatalar oluşur.

import React, { useCallback, useMemo, useState } from "react";

const Row = React.memo(function Row({ item, onSelect }) {
  console.log("Row render:", item.id);
  return (
    <button onClick={() => onSelect(item.id)}>
      {item.title}
    </button>
  );
});

export default function ListPage({ rawItems }) {
  const [query, setQuery] = useState("");

  const items = useMemo(() => {
    const q = query.trim().toLowerCase();
    if (!q) return rawItems;
    return rawItems.filter(x => x.title.toLowerCase().includes(q));
  }, [rawItems, query]);

  const onSelect = useCallback((id) => {
    console.log("selected:", id);
  }, []);

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      {items.map((item) => (
        <Row key={item.id} item={item} onSelect={onSelect} />
      ))}
    </div>
  );
}

Bu örnekte iki hedef var: Filtre hesaplamasını gereksiz yere tekrarlamamak ve Row bileşenine her render’da yeni fonksiyon göndermemek. React.memo tek başına yetmez; props referansları da stabil olmalı.

Beyaz tahtada memoization akışı çizilmiş, yanında useMemo ve useCallback etiketleriyle dependency notları

Context ve state mimarisini sadeleştirmek ve bölmek

Bir uygulamada performans şikâyetlerinin büyük kısmı “her şey tek context’te” ya da “tek store’dan her yer besleniyor” gibi geniş kapsamlı güncelleme alanlarından gelir. Context güncellemesi, o context’i tüketen bileşenleri tetikler; dolayısıyla context tasarımında sınır çizmek kritik olur.

Context değerini bölerek tüketimi küçültmek

Tek bir “AppContext” içinde kullanıcı, tema, sepet ve izinler gibi birçok değer varsa; küçük bir değişim tüm tüketicileri etkileyebilir. Çözüm, context’i alanlara ayırmak ve mümkünse sadece ihtiyaç duyulan alt ağaca provider koymaktır. Böylece render dalgasını daraltmak mümkün olur.

Derived state kullanımını azaltarak karmaşayı önlemek

“State içinde state” gibi durumlar (örneğin filtrelenmiş listeyi ayrıca state’te tutmak) çakışmaları artırır. Çoğu derived veri, render sırasında hesaplanabilir ve useMemo ile optimize edilebilir. Bu yaklaşım, hem hata riskini azaltır hem de güncellemeleri daha öngörülebilir kılar.


Key ve liste yönetimini doğru kurgulamak ve stabil tutmak

Listelerdeki key stratejisi, React’in hangi elemanın “aynı” olduğunu anlaması için hayati önem taşır. Yanlış key, React’in elemanları yeniden yaratmasına, input odaklarının kaymasına ve gereksiz DOM işlemlerine yol açabilir.

Index yerine kalıcı kimlik kullanarak eşleşmek

Index key kullanımı; sıralama değiştiğinde, ekleme/çıkarma olduğunda eleman kimliğini bozar. Sonuç: React, elemanları yanlış eşleştirir ve “gereksiz render” hissi artar. Kalıcı ve benzersiz bir id ile eşleşmek daha güvenilir olur.

Liste bileşenini bölerek güncellemeyi daraltmak

Çok büyük listelerde tek bileşen içinde hem filtre hem satır render’ı yönetmek, değişimi genişletir. Listeyi “kontrol paneli + liste gövdesi + satır” olarak bölmek ve satırı memoize etmek, güncelleme alanını daraltmaya yardımcı olur. Ayrıca sanallaştırma (virtualization) gibi teknikler, DOM maliyetini düşürmekte etkilidir.


Profiling alışkanlığını kurmak ve kök nedeni bulmak

“Memo ekledim hızlandı mı?” sorusunun yanıtını tahminle değil, ölçümle vermek gerekir. React DevTools Profiler, hangi bileşenin ne kadar sürdüğünü ve neden render aldığını görmenize yardım eder. Burada hedef, en çok zaman harcayan ve en sık tetiklenen noktaları bulmaktır.

Profiler çıktısını okuyarak aksiyon almak

Profiler’da “Commit duration” ve bileşen ağaçları üzerinden ilerleyip, render zincirini takip edebilirsiniz. “Neden render oldu?” bilgisini yorumlayınca, gerçek çözüm genellikle basit çıkar: stabil props, daha küçük context, doğru key ya da daha iyi state sınırı.

Günlük geliştirmede ölçümlemeyi rutine eklemek

Her performans sorununda tüm uygulamayı profil etmek zorunda değilsiniz. Lokal bir sayfayı izole edip, render log’ları ve Profiler ile küçük bir döngü kurmak yeterli olur. Aşağıdaki mini yardımcı, hangi prop değişince yeniden render olduğunu yakalamayı kolaylaştırır.

import { useEffect, useRef } from "react";

export function useWhyDidYouUpdate(name, props) {
  const prev = useRef(props);

  useEffect(() => {
    const changed = Object.keys({ ...prev.current, ...props }).reduce((acc, key) => {
      if (prev.current[key] !== props[key]) acc[key] = { from: prev.current[key], to: props[key] };
      return acc;
    }, {});
    if (Object.keys(changed).length) {
      console.log("[why-did-you-update]", name, changed);
    }
    prev.current = props;
  });
}

Bu yardımcıyı kritik bileşenlerde kısa süreli kullanıp “hangi prop referansı değişiyor?” sorusuna net yanıt alabilirsiniz. Sonrasında gereksiz render’ı kök neden üzerinden azaltmak çok daha hızlı ilerler.

Ekranda bileşen ağacı ve render sayıları gösteren panel, yanında not defterinde optimizasyon maddeleri yazılı

Kapanış: Gereksiz render azaltmak için önce React’in render ve commit sürecini anlamak, sonra tetikleyicileri sınıflandırmak gerekir. Ardından memoization’ı ölçerek uygulamak, context ve state sınırlarını daraltmak, key stratejisini doğru kurgulamak ve profiling’i rutinleştirmek hızlı kazanımlar sağlar. Pratik egzersiz ve örnek projelerle ilerlemek isterseniz react eğitimi içeriği bu konuları adım adım pekiştirmenize yardımcı olur.

 Vimaj