İçeriğe geç
react

React Render Optimizasyonu: Gereksiz Render'ları Önleme

Tarık Tunç
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>
  );
});
Uyarı: useMemo ve useCallback'i her yerde kullanmak da bir anti-pattern'dir. Bu hook'ların kendileri de bir maliyet taşır (bellek + karşılaştırma maliyeti). Sadece gerçekten performans sorunu olan yerlerde kullanın.

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"
    />
  );
}
İpucu: React DevTools'ta "Why did this render?" özelliğini kullanarak, bir component'in neden render olduğunu detaylı şekilde görebilirsiniz. Bu, optimizasyon stratejinizi belirlemede çok yardımcı olur.

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.

Bilgi: React Compiler (React Forget), gelecekte useMemo ve useCallback ihtiyacını ortadan kaldıracak otomatik memoizasyon sağlayacaktır. Ancak şu an için manuel optimizasyon hala gereklidir.

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.

Kaynaklar

İlgili Yazılar