İçeriğe geç
react

React.memo ve Derin Karşılaştırma Stratejileri

Tarık Tunç
React.memo ve Derin Karşılaştırma Stratejileri

React.memo ve Derin Karşılaştırma Stratejileri

React uygulamalarında performans optimizasyonu kritik bir konudur. React.memo, fonksiyonel bileşenleri memoize ederek gereksiz yeniden render'ları önler. Ancak doğru kullanılmadığında beklenen faydayı sağlamayabilir. Bu yazıda React.memo'nun çalışma prensibini, derin karşılaştırma stratejilerini ve en iyi uygulamaları detaylı olarak inceleyeceğiz.

React.memo Nedir?

React.memo, bir Higher-Order Component (HOC) olup fonksiyonel bileşenleri sarmalayarak prop'lar değişmediğinde yeniden render'ı engeller. Varsayılan olarak sığ (shallow) karşılaştırma yapar.

import { memo } from 'react';

// Temel kullanım
const UserCard = memo(function UserCard({ name, email, avatar }) {
  console.log('UserCard render edildi');
  return (
    <div className="user-card">
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  );
});

// Parent bileşen her render olduğunda,
// UserCard'ın prop'ları değişmediyse tekrar render edilmez
function UserList() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      {/* count değişse bile UserCard tekrar render edilmez */}
      <UserCard name="Ahmet" email="ahmet@mail.com" avatar="/avatar.jpg" />
    </div>
  );
}

Shallow (Sığ) Karşılaştırma Nasıl Çalışır?

React.memo varsayılan olarak Object.is ile sığ karşılaştırma yapar. Bu, her prop'un referans eşitliğini kontrol eder:

// Object.is karşılaştırma örnekleri
Object.is('hello', 'hello');    // true — string eşitliği
Object.is(42, 42);              // true — sayı eşitliği
Object.is(true, true);          // true — boolean eşitliği

// Referans tipleri — her seferinde yeni referans
Object.is({ a: 1 }, { a: 1 }); // false! — farklı referanslar
Object.is([1, 2], [1, 2]);     // false! — farklı referanslar

// Fonksiyonlar
const fn1 = () => {};
const fn2 = () => {};
Object.is(fn1, fn2);           // false! — farklı referanslar
Object.is(fn1, fn1);           // true — aynı referans
Uyarı: Obje, dizi veya fonksiyon prop'ları her render'da yeni referans oluşturur. Bu durumda React.memo etkisiz hale gelir. Bu sorunu çözmek için useMemo ve useCallback kullanılmalıdır.

Referans Stabilitesi Sağlama

import { memo, useMemo, useCallback } from 'react';

const DataTable = memo(function DataTable({ data, columns, onRowClick }) {
  return (
    <table>
      <thead>
        <tr>
          {columns.map((col) => (
            <th key={col.key}>{col.label}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {data.map((row) => (
          <tr key={row.id} onClick={() => onRowClick(row)}>
            {columns.map((col) => (
              <td key={col.key}>{row[col.key]}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
});

function Dashboard() {
  const [filter, setFilter] = useState('');
  const [selectedRow, setSelectedRow] = useState(null);

  // useMemo ile obje/dizi referansını stabilize et
  const columns = useMemo(() => [
    { key: 'name', label: 'İsim' },
    { key: 'email', label: 'E-posta' },
    { key: 'role', label: 'Rol' },
  ], []);

  const filteredData = useMemo(() =>
    users.filter((u) => u.name.includes(filter)),
    [filter]
  );

  // useCallback ile fonksiyon referansını stabilize et
  const handleRowClick = useCallback((row) => {
    setSelectedRow(row);
  }, []);

  return (
    <div>
      <input value={filter} onChange={(e) => setFilter(e.target.value)} />
      <DataTable
        data={filteredData}
        columns={columns}
        onRowClick={handleRowClick}
      />
    </div>
  );
}

Özel Karşılaştırma Fonksiyonu

React.memo'ya ikinci parametre olarak özel bir karşılaştırma fonksiyonu geçebilirsiniz. Bu fonksiyon, derin karşılaştırma veya belirli prop'ları görmezden gelme gibi senaryolarda kullanılır.

const Chart = memo(
  function Chart({ data, config, onHover }) {
    // Ağır render işlemi
    return <canvas ref={canvasRef} />;
  },
  (prevProps, nextProps) => {
    // true döndürmek = render'ı atla (prop'lar eşit)
    // false döndürmek = tekrar render et (prop'lar farklı)

    // Sadece data ve config'i karşılaştır, onHover'ı görmezden gel
    return (
      prevProps.data === nextProps.data &&
      prevProps.config.type === nextProps.config.type &&
      prevProps.config.theme === nextProps.config.theme
    );
  }
);

// Derin karşılaştırma örneği
import isEqual from 'lodash/isEqual';

const DeepComparedList = memo(
  function DeepComparedList({ items, options }) {
    return (
      <ul>
        {items.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    );
  },
  (prevProps, nextProps) => {
    return isEqual(prevProps, nextProps);
  }
);
İpucu: Derin karşılaştırma (deep comparison) maliyetli bir işlemdir. lodash/isEqual gibi kütüphaneler kullanılabilir, ancak büyük ve derin nesnelerde karşılaştırma maliyeti, render maliyetinden fazla olabilir. Önce ölçün, sonra optimize edin.

Ne Zaman React.memo Kullanmalı?

Kullanın Kullanmayın
Ağır render işlemleri olan bileşenler Zaten hızlı render edilen küçük bileşenler
Aynı prop'larla sık render edilen bileşenler Her render'da farklı prop alan bileşenler
Büyük listelerde her bir liste öğesi Primitive prop alan basit bileşenler
Üst bileşen sık render olduğunda Children prop kullanan wrapper bileşenler

React Compiler ve Otomatik Memoization

React 19 ile birlikte gelen React Compiler, bileşenleri ve hook'ları otomatik olarak memoize eder. Bu, gelecekte React.memo, useMemo ve useCallback kullanımına olan ihtiyacı büyük ölçüde azaltacaktır.

// React Compiler öncesi — manuel memoization
const MemoizedList = memo(function List({ items }) {
  const sorted = useMemo(() => items.sort(), [items]);
  const handleClick = useCallback((id) => {
    console.log(id);
  }, []);
  return sorted.map(item => <Item key={item.id} onClick={handleClick} />);
});

// React Compiler sonrası — otomatik memoization
// Compiler aynı optimizasyonları otomatik uygular
function List({ items }) {
  const sorted = items.sort();
  const handleClick = (id) => console.log(id);
  return sorted.map(item => <Item key={item.id} onClick={handleClick} />);
}

Performans Profiling

React.memo'nun gerçekten fayda sağladığını doğrulamak için React DevTools Profiler kullanın:

// React DevTools Profiler ile ölçüm
import { Profiler } from 'react';

function onRenderCallback(id, phase, actualDuration) {
  console.log(`${id} — ${phase} — ${actualDuration}ms`);
}

function App() {
  return (
    <Profiler id="DataTable" onRender={onRenderCallback}>
      <DataTable data={data} />
    </Profiler>
  );
}

Anti-Pattern'ler

// Anti-pattern 1: Her render'da yeni obje oluşturma
function Parent() {
  return (
    <MemoizedChild
      style={{ color: 'red' }}  // Her render'da yeni referans!
      data={[1, 2, 3]}          // Her render'da yeni dizi!
    />
  );
}

// Anti-pattern 2: Inline fonksiyon
function Parent() {
  return (
    <MemoizedChild
      onClick={() => console.log('click')}  // Her render'da yeni fonksiyon!
    />
  );
}

// Anti-pattern 3: Children geçirme
function Parent() {
  return (
    <MemoizedWrapper>
      <span>İçerik</span>  {/* Her render'da yeni JSX! */}
    </MemoizedWrapper>
  );
}
Bilgi: React.memo sadece prop değişikliklerini kontrol eder. Bileşen kendi state'ini (useState) veya context'ini (useContext) kullanıyorsa, bunlar değiştiğinde memo'dan bağımsız olarak yeniden render edilir.

Sonuç

React.memo, doğru kullanıldığında performans kazancı sağlayan güçlü bir optimizasyon aracıdır. Sığ karşılaştırmanın sınırlamalarını anlamak, referans stabilitesini useMemo ve useCallback ile sağlamak ve gerektiğinde özel karşılaştırma fonksiyonları yazmak başarılı kullanımın anahtarıdır. Her bileşeni memo ile sarmak yerine, önce React DevTools Profiler ile darboğazları tespit edin ve sadece ihtiyaç duyulan yerlerde optimize edin. React Compiler'ın yaygınlaşmasıyla manuel memoization ihtiyacı azalacak olsa da, bu kavramları anlamak her React geliştiricisi için değerlidir.

Kaynaklar

İlgili Yazılar