useMemo & useCallback
Şimdiye kadar useState, useEffect ve useRef'i gördük. Bu ders, iki
performans hook'unu -- useMemo ve useCallback -- anlatıyor. Bu ikisi
diğerlerinden biraz farklı: kullanmayı bilmek kadar, NE ZAMAN
kullanmaman gerektiğini bilmek de önemli.
Memoization Nedir?
Memoization, bir hesaplamanın sonucunu hafızada tutup, aynı girdilerle
tekrar istenirse hesaplamayı TEKRARLAMADAN o sonucu döndürme tekniğidir.
React'te component'ler sık sık yeniden render olur; her render'da aynı
pahalı hesaplamayı ya da aynı fonksiyonu yeniden oluşturmak, bazı
durumlarda gereksiz bir maliyet olabilir -- useMemo ve useCallback bu
maliyeti önlemek için var.
useMemo ile Hesaplama Sonucunu Önbelleğe Almak
useMemo, bir hesaplamanın SONUCUNU önbelleğe alır:
import { useState, useMemo } from "react";
function slowSquare(n) {
// Kasıtlı olarak YAVAŞ bir hesaplama -- gerçek bir "pahalı hesaplama"yı
// simüle etmek için.
let result = 0;
for (let i = 0; i < 100000000; i++) {
result = n * n;
}
return result;
}
function UseMemoBasicExample() {
const [number, setNumber] = useState(5);
const [theme, setTheme] = useState("light");
// useMemo, `number` değişmediği sürece slowSquare'i TEKRAR ÇALIŞTIRMAZ --
// önceki sonucu hafızada tutar (memoize eder) ve onu döndürür.
const squared = useMemo(() => slowSquare(number), [number]);
return (
<div>
<p>
{number} karesi: {squared}
</p>
<button onClick={() => setNumber(number + 1)}>Sayıyı Artır</button>
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Temayı Değiştir ({theme})
</button>
</div>
);
}
useMemo(() => slowSquare(number), [number]), number değişmediği
sürece slowSquare'i TEKRAR ÇALIŞTIRMAZ -- önceki sonucu döndürür. Temayı
değiştirmek component'i yeniden render eder, ama number aynı kaldığı
için yavaş hesaplama tekrar çalışmaz.
useMemo ile Nesne Referansını Sabit Tutmak
useMemo, yalnızca "pahalı" hesaplamalar için değil, bir nesnenin/dizinin
REFERANSINI (kimliğini) render'lar arasında sabit tutmak için de
kullanılır:
import { useState, useMemo } from "react";
function ObjectMemoizationExample() {
const [count, setCount] = useState(0);
// useMemo olmadan, her render'da YENİ bir nesne oluşur -- içeriği aynı
// olsa bile, iki farklı render'daki nesneler "aynı" (===) SAYILMAZ.
const config = useMemo(
() => ({ theme: "dark", fontSize: 16 }),
[], // Bağımlılık yok -- nesne yalnızca İLK render'da oluşturulur.
);
return (
<div>
<p>Sayaç: {count}</p>
<p>
Tema: {config.theme}, Font Boyutu: {config.fontSize}
</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
useMemo olmadan, her render'da { theme: "dark", fontSize: 16 } YENİ
bir nesne olarak oluşur -- içeriği aynı olsa bile, JavaScript'te iki ayrı
nesne === ile karşılaştırıldığında eşit sayılmaz. useMemo, dependency
array değişmediği sürece AYNI nesneyi döndürür.
useCallback ile Fonksiyon Referansını Sabit Tutmak
useCallback, useMemo'ya çok benzer, ama bir DEĞER yerine bir
FONKSİYONU önbelleğe alır:
import { useState, useCallback } from "react";
function UseCallbackBasicExample() {
const [count, setCount] = useState(0);
// useCallback olmadan, her render'da handleClick YENİ bir fonksiyon olarak
// oluşturulur -- iki farklı render'daki fonksiyonlar "aynı" sayılmaz.
// useCallback, dependency array değişmediği sürece AYNI fonksiyon
// referansını korur.
const handleClick = useCallback(() => {
console.log("Tıklandı, count:", count);
}, [count]);
return (
<div>
<p>Sayaç: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={handleClick}>Console'a Yaz</button>
</div>
);
}
useCallback olmadan, her render'da handleClick YENİ bir fonksiyon
olarak oluşturulur. useCallback, dependency array ([count])
değişmediği sürece AYNI fonksiyon referansını korur.
Ne Zaman KULLANILMAMALI?
useMemo ve useCallback'in kendi maliyeti var -- dependency array'i
karşılaştırmak ve sonucu hafızada tutmak. Basit, hızlı işlemler için bu
maliyet, kazandırdığından daha pahalı olabilir:
import { useState, useMemo } from "react";
function WhenNotToUseMemoExample() {
const [count, setCount] = useState(0);
// GEREKSİZ: Bu toplama işlemi zaten çok hızlı -- useMemo'nun kendi
// maliyeti (karşılaştırma yapmak, sonucu hafızada tutmak), kazandırdığından
// daha pahalı olabilir.
// const doubled = useMemo(() => count * 2, [count]);
// DAHA İYİ: Basit, hızlı hesaplamalar için useMemo'ya gerek yok.
const doubled = count * 2;
return (
<div>
<p>
{count} x 2 = {doubled}
</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
count * 2 gibi basit bir işlemi useMemo ile sarmalamanın hiçbir
faydası yok -- işlem zaten neredeyse anlık. useMemo/useCallback'i
yalnızca gerçekten pahalı bir hesaplama varsa, ya da bir değerin/
fonksiyonun referansının sabit kalması (örneğin başka bir hook'un
dependency array'inde kullanılacağı için) gerektiğinde kullan. Aksi
halde kodun daha karmaşık, ama daha hızlı OLMAYAN bir hâli ortaya çıkar --
buna "erken optimizasyon" (premature optimization) denir.
Özet ve Terimler Sözlüğü
useMemo, bir hesaplamanın sonucunu ya da bir nesnenin referansını
önbelleğe alır; useCallback aynı şeyi bir fonksiyon için yapar. İkisi
de dependency array değişmediği sürece önceki sonucu/referansı döndürür.
Basit, hızlı işlemler için bu hook'lara gerek yok -- kendi maliyetleri,
çözdükleri sorundan daha büyük olabilir.
Terimler Sözlüğü
Memoization — Bir hesaplamanın sonucunu hafızada tutup, aynı girdilerle tekrar istendiğinde hesaplamayı tekrarlamadan döndürme tekniği.
useMemo — Bir hesaplamanın sonucunu ya da bir nesne/dizi
referansını memoize eden hook.
useCallback — Bir fonksiyon referansını memoize eden hook.
Premature Optimization (Erken Optimizasyon) — Gerçekten gerekmeden, performans kazancı sağlamayan ama kodu karmaşıklaştıran bir optimizasyon yapmak.