useCallback, bir fonksiyonu render'lar arasında önbelleğe alır: bağımlılıklarından biri değişene kadar aynı fonksiyon nesnesini döndürür. Bu yalnızca bir şey fonksiyonu referansa göre karşılaştırdığında önemlidir; pratikte bu, memo ile sarılmış bir çocuk ya da fonksiyonu bağımlılık olarak listeleyen bir efekt demektir.
İki düğme de memoize edilmiştir ve ikisi de aynı şeyi yapar. Temayı değiştir ve Console'u izle: yalnızca render Inline function görünür, çünkü saveInline her render'da yeni bir fonksiyondur, bu yüzden memo değişmiş bir prop görür. saveCached her seferinde aynı fonksiyondur, bu yüzden o düğme atlanır.
Neyi önbelleğe alır
const cachedFn = useCallback(fn, dependencies);
fntutmak istediğin fonksiyondur. React onu çağırmaz; sana geri döndürür.dependencies,fn'in okuduğu, bileşenden gelen her değeri listeler. Her render'da React onları öncekilerleObject.iskullanarak karşılaştırır.- Hiçbir şey değişmediyse önceki render'daki fonksiyonu alırsın. Bir şey değiştiyse bu sefer aktardığın fonksiyonu alırsın ve React bir dahaki sefer için onu tutar.
Bunun ne anlama geldiğine dikkat et: useCallback içindeki ok fonksiyonu her render'da yine de oluşturulur, çünkü o sadece bir argümandır. useCallback bu işi kurtarmaz. Tek işi, referans aynı kalsın diye daha eski, özdeş bir fonksiyonu geri vermektir.
useCallback ve useMemo
Aynı mekanizmadırlar. useMemo bir fonksiyonu çağırmanın sonucunu, useCallback ise fonksiyonun kendisini önbelleğe alır:
// These two lines are equivalent
const handleSave = useCallback(() => save(draft), [draft]);
const handleSave = useMemo(() => () => save(draft), [draft]);
Daha iyi okunduğu için fonksiyonlar için useCallback'i, filtrelenmiş bir liste ya da bir seçenekler nesnesi gibi hesaplanmış değerler için useMemo'yu kullan.
Updater fonksiyonlarıyla bağımlılıkları düşürmek
Önbelleğe alınmış bir fonksiyon okuduğu her state değerini listelemek zorundadır ve o state her değiştiğinde fonksiyon da değişir. Memoize edilmiş satırlardan oluşan bir liste için bu bir sorundur: todos'u okuyan bir remove fonksiyonu todos her değiştiğinde değişir, bu yüzden kalan her satır her silmeden sonra yeniden render olur. Setter'ın updater biçimini kullan, böylece fonksiyon artık todos'u hiç okumaz.
Bir satırı sil: Console sessiz kalır, çünkü diğer satırlar öncekiyle aynı todo nesnesini ve aynı remove fonksiyonunu aldı. Fonksiyonu useCallback((id) => setTodos(todos.filter((t) => t.id !== id)), [todos]) olarak değiştir ve yeniden dene: artık kalan her satır her silmeden sonra bir render kaydeder.
Efekt bağımlılığı olarak fonksiyonlar
Bileşende tanımlanmış bir fonksiyonu çağıran bir efekt, o fonksiyonu bir bağımlılık olarak listelemek zorundadır. Fonksiyon her render'da yeniyse efekt her render'dan sonra çalışır. useCallback onu kendi girdileri değişene kadar sabit tutar:
Bir mesaj yazmak yeniden bağlanmaya yol açmaz. Oda değiştirmek eski odadan bir bağlantı kesmeyi ve yeni odaya bir bağlanmayı kaydeder. useCallback sarmalayıcısını sil (ok fonksiyonunu koru); her tuş vuruşu bağlantıyı kesip yeniden bağlanır.
Ama burada daha basit bir çözüm var: fonksiyonu efektin içine taşı. O zaman efekt doğrudan roomId'ye bağlı olur ve hiçbir fonksiyonun sabit olması gerekmez.
useEffect(() => {
function createOptions() {
return { serverUrl: 'wss://chat.example', roomId };
}
const options = createOptions();
// ...
}, [roomId]);
Fonksiyon yalnızca efekt tarafından kullanılıyorsa bunu tercih et. useCallback'i aşağı da aktardığın ya da bir özel hook'tan döndürdüğün fonksiyonlar için sakla; orada çağıranlar onları kendi bağımlılık dizilerine koyabilir.
Bir fonksiyonun değişip değişmediğini kontrol etmek
Bir memo çocuğu render olmaya devam ettiğinde ve nedenini bilmediğinde, fonksiyon prop'unun render'lar arasında gerçekten aynı nesne olup olmadığını kontrol et. Öncekini bir ref'te tut ve karşılaştır:
Count'a tıkla: Console increment changed: false der, çünkü step değişmedi. Step'e tıkla, true der: step bir bağımlılıktır, bu yüzden React yeni fonksiyonu geri verdi. Her render'da değişen bir fonksiyon genellikle bir bağımlılığın kendisinin her render'da yeni olduğu anlamına gelir; örneğin bileşende tanımlanmış bir nesne ya da başka bir fonksiyon.
Özel hook'larda useCallback
Fonksiyon döndüren bir özel hook, onu kimin çağıracağını bilmesen bile useCallback için iyi bir yerdir. Hook'u kullanan bileşen bu fonksiyonları bir memo çocuğuna aktarabilir ya da bir efektte listeleyebilir ve onları dışarıdan sabit yapamaz. Onları hook'un içinde sarmak her çağırana varsayılan olarak sabit bir referans verir:
function useCounter(initial = 0) {
const [count, setCount] = useState(initial);
const increment = useCallback(() => setCount((c) => c + 1), []);
const reset = useCallback(() => setCount(initial), [initial]);
return { count, increment, reset };
}
Yaygın hata: her şeyi sarmak
Doğrudan bir <button>'a ya da <input>'a giden bir işleyicinin etrafındaki useCallback işe yarar hiçbir şey yapmaz. React, işleyicisi aynı kaldı diye bir DOM öğesi için hiçbir işi atlamaz ve memo ile sarılmamış bir bileşen ebeveyni render olduğunda zaten render olur.
// Pointless: <button> does not care that the function is the same
const handleClick = useCallback(() => setOpen(true), []);
return <button onClick={handleClick}>Open</button>;
Her ekstra useCallback birinin yanlış yapabileceği bir bağımlılık dizisi ekler ve eskimiş bir bağımlılık gerçek bir hatadır (fonksiyon eski state'i okumaya devam eder). Onu varsayılan olarak değil, alıcı tarafta bir memo çocuğu ya da bir bağımlılık dizisi olduğunda ekle.
Derlemen kullandığında React Compiler 1.0 fonksiyonları ve değerleri otomatik olarak memoize eder; bu da elle yazılan useCallback çağrılarının çoğuna olan ihtiyacı ortadan kaldırır. Compiler olmadan yukarıdaki kurallar hâlâ geçerlidir ve manuel useCallback mevcut kodda hâlâ yaygındır.
Sıkça Sorulan Sorular
useCallback ne yapar?
Bağımlılıklarından biri değişene kadar her render'da aynı fonksiyon nesnesini döndürür. Fonksiyonun oluşturulmasını engellemez; yenisi yerine daha önceki bir render'daki fonksiyonu geri verir.
useCallback'i ne zaman kullanmalıyım?
memo ile sarılmış bir çocuğa bir fonksiyon aktardığında ya da fonksiyon bir efektin veya başka bir hook'un bağımlılığı olduğunda. İki durumda da her render'da yeni bir fonksiyon optimizasyonu boşa çıkarır ya da efekti yeniden çalıştırır.
useCallback ile useMemo arasındaki fark nedir?
useCallback(fn, deps), fn'in kendisini önbelleğe alır. useMemo(() => value, deps) bir fonksiyonun döndürdüğü değeri önbelleğe alır. useCallback(fn, deps) tam olarak useMemo(() => fn, deps)'tir.
Her fonksiyonu useCallback ile sarmalı mıyım?
Hayır. Fonksiyon düz bir DOM öğesine ya da memoize edilmemiş bir bileşene gidiyorsa sabit bir referans hiçbir şeyi değiştirmez, ekstra bağımlılık dizileri de kodu okumayı zorlaştırır ve hata yapmayı kolaylaştırır.
useCallback bileşenimi hızlandırır mı?
Tek başına değil. Yalnızca aşağıdaki bir şey fonksiyonu referansa göre karşılaştırdığında işe yarar, örneğin memo ya da bir efektin bağımlılık dizisi.