useMemo yeniden render işlemleri arasında bir hesaplamanın sonucunu önbelleğe almanızı sağlayan bir React Hook’udur.

const cachedValue = useMemo(calculateValue, dependencies)

Referans

useMemo(calculateValue, dependencies)

Yeniden render işlemleri arasında bir hesaplamayı önbelleğe almak için bileşeninizin en üst seviyesinde useMemo’yu çağırın:

import { useMemo } from 'react';

function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}

Aşağıda daha fazla örnek görebilirsiniz.

Parametreler

  • calculateValue: Önbelleğe almak istediğiniz değeri hesaplayan fonksiyon. Saf fonksiyon olmalı, argüman almamalı ve herhangi bir türde bir değer döndürmelidir. React ilk render sırasında fonksiyonunuzu çağıracaktır. Sonraki render’larda, bağımlılıklar (dependencies) son render’dan bu yana değişmediyse React aynı değeri tekrar döndürecektir. Aksi takdirde, calculateValue’yu çağıracak, sonucunu döndürecek ve daha sonra tekrar kullanılabilmesi için saklayacaktır.

  • dependencies: calculateValue kodu içinde referans edilen tüm reaktif değerlerin listesidir. Reaktif değerler; prop’ları, state’i ve doğrudan bileşen gövdenizin içinde tanımlanan tüm değişkenleri ve fonksiyonları içerir. Eğer linter’ınız React için yapılandırılmışsa, her reaktif değerin bir bağımlılık olarak doğru şekilde belirtildiğini doğrulayacaktır. Bağımlılıklar listesi sabit sayıda öğeye sahip olmalı ve [dep1, dep2, dep3] gibi satır içi yazılmalıdır. React, Object.is karşılaştırmasını kullanarak her bağımlılığı önceki değeriyle karşılaştıracaktır.

Döndürülen Değerler

İlk render işleminde useMemo, calculateValue çağrısının sonucunu hiçbir argüman olmadan döndürür.

Sonraki render işlemleri sırasında, ya son render işleminde depolanmış olan değeri döndürür (bağımlılıklar değişmediyse) ya da calculateValue fonksiyonunu tekrar çağırır ve calculateValue ‘nun döndürdüğü sonucu döndürür.

Uyarılar

  • useMemo` bir Hook’tur, bu nedenle onu yalnızca bileşeninizin en üst seviyesinde veya kendi Hook’larınızda çağırabilirsiniz. Döngülerin veya koşulların içinde çağıramazsınız. Buna ihtiyacınız varsa, yeni bir bileşen çıkarın ve state’i içine taşıyın.
  • Strict Mod’da React, yanlışlıkla oluşan safsızlıkları bulmanıza yardımcı olmak için hesaplama fonksiyonunuzu iki kez çağıracaktır. Bu yalnızca geliştirmeye yönelik bir davranıştır ve canlı ortamı etkilemez. Hesaplama fonksiyonunuz safsa (olması gerektiği gibi), bu durum mantığınızı etkilememelidir. Çağrılardan birinin sonucu göz ardı edilecektir.
  • React özel bir neden olmadıkça önbelleğe alınan değeri atmayacaktır. Örneğin, geliştirme sırasında, bileşeninizin dosyasını düzenlediğinizde React önbelleği atar. Hem geliştirme hem de canlı ortamda, bileşeniniz ilk mount sırasında askıya alınırsa React önbelleği atacaktır. Gelecekte React, önbelleğin atılmasından yararlanan daha fazla özellik ekleyebilir - örneğin, React gelecekte sanallaştırılmış listeler için yerleşik destek eklerse, sanallaştırılmış tablo görünüm alanından dışarı kaydırılan öğeler için önbelleği atmak mantıklı olacaktır. Eğer useMemo’ya sadece bir performans optimizasyonu olarak güveniyorsanız bu bir sorun olmayacaktır. Aksi takdirde, bir state değişkeni veya bir ref daha uygun olabilir.

Not

Dönüş değerlerinin bu şekilde önbelleğe alınması memoization olarak da bilinir, bu yüzden bu Hook useMemo olarak adlandırılmıştır.


Kullanım

Maliyetli yeniden hesaplamaların atlanması

Bir hesaplamayı yeniden render işlemleri arasında önbelleğe almak için, bileşeninizin en üst seviyesinde bir useMemo çağrısına sarın:

import { useMemo } from 'react';

function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}

useMemo’ya iki parametre geçmeniz gerekir:

  1. () => gibi hiçbir argüman almayan ve hesaplamak istediğiniz şeyi döndüren bir hesaplama fonksiyonu.
  2. Bileşeniniz içinde hesaplamanızda kullanılan her değeri içeren bir bağımlılıklar listesi.

İlk render işleminde useMemo’dan alacağınız değer, hesaplamanızın çağrılmasının sonucu olacaktır.

Sonraki her render işleminde React, bağımlılıkları son render sırasında ilettiğiniz bağımlılıklarla karşılaştıracaktır. Bağımlılıkların hiçbiri (Object.is ile karşılaştırıldığında) değişmediyse, useMemo daha önce hesapladığınız değeri döndürür. Aksi takdirde, React hesaplamanızı yeniden çalıştıracak ve yeni değeri döndürecektir.

Başka bir deyişle, useMemo, bağımlılıkları değişene kadar bir hesaplama sonucunu yeniden render işlemleri arasında önbelleğe alır.

Bunun ne zaman yararlı olduğunu görmek için bir örnek üzerinden gidelim.

Varsayılan olarak, React her yeniden render edildiğinde bileşeninizin tüm gövdesini yeniden çalıştıracaktır. Örneğin, TodoList state’ini güncellerse veya üstünden yeni prop’lar alırsa, filterTodos fonksiyonu yeniden çalışacaktır:

function TodoList({ todos, tab, theme }) {
const visibleTodos = filterTodos(todos, tab);
// ...
}

Genelde çoğu hesaplama çok hızlı olduğu için bu bir sorun teşkil etmez. Ancak, büyük bir diziyi filtreliyor veya dönüştürüyorsanız ya da maliyetli bir hesaplama yapıyorsanız, veriler değişmediyse tekrar hesaplamayı atlamak isteyebilirsiniz. Hem todos hem de tab son render sırasında olduğu gibi aynıysa, hesaplamayı useMemo’ya sarmak, daha önce hesapladığınız visibleTodos’u yeniden kullanmanızı sağlar.

Bu tür önbelleğe alma işlemine memoization adı verilir.

Not

useMemo’ya yalnızca bir performans optimizasyonu olarak güvenmelisiniz. Kodunuz onsuz çalışmıyorsa, önce altta yatan sorunu bulun ve düzeltin. Daha sonra performansı artırmak için useMemo ekleyebilirsiniz.

Derinlemesine İnceleme

Bir hesaplamanın maliyetli olup olmadığı nasıl anlaşılır?

Genel olarak, binlerce nesne oluşturmadığınız veya üzerinde döngü yapmadığınız sürece, hesaplama muhtemelen maliyetli değildir. Emin olmak istiyorsanız, bir kod parçasında harcanan zamanı ölçmek için bir console log ekleyebilirsiniz:

console.time('filter array');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array');

Ölçtüğünüz etkileşimi gerçekleştirin (örneğin, input’a yazmak). Daha sonra konsolunuzda filter array: 0.15ms gibi kayıtlar göreceksiniz. Kaydedilen toplam süre önemli bir miktara ulaşıyorsa (örneğin, 1ms veya daha fazla), bu hesaplamayı hafızaya almak mantıklı olabilir. Bir deneme olarak, bu etkileşimde toplam kaydedilen sürenin azalıp azalmadığını doğrulamak için hesaplamayı useMemo içine sarabilirsiniz:

console.time('filter array');
const visibleTodos = useMemo(() => {
return filterTodos(todos, tab); // Todos ve tab değişmediyse atlanır
}, [todos, tab]);
console.timeEnd('filter array');

useMemo ilk render işlemini hızlandırmaz. Sadece güncellemeler üzerindeki gereksiz çalışmaları atlamanıza yardımcı olur.

Makinenizin muhtemelen kullanıcılarınızınkinden daha hızlı olduğunu unutmayın, bu nedenle performansı yapay bir yavaşlatma ile test etmek iyi bir fikirdir. Örneğin, Chrome bunun için bir CPU Throttling seçeneği sunar.

Ayrıca, geliştirme sırasında performans ölçümünün size en doğru sonuçları vermeyeceğini unutmayın. (Örneğin, Strict Mod açık olduğunda, her bileşenin bir yerine iki kez işlendiğini göreceksiniz). En doğru zamanlamaları elde etmek için uygulamanızı canlı ortam için oluşturun ve kullanıcılarınızın sahip olduğu gibi bir cihazda test edin.

Derinlemesine İnceleme

Her yere useMemo’yu eklemeli misiniz?

Uygulamanız bu site gibiyse ve etkileşimler detaylı değilse (bir sayfayı veya tüm bir bölümü değiştirmek gibi), memoization genellikle gereksizdir. Öte yandan, uygulamanız daha çok bir çizim editörüne benziyorsa ve etkileşimlerin çoğu ayrıntılı ise (şekilleri taşımak gibi), o zaman memoization’ı çok yararlı bulabilirsiniz.

useMemo ile optimizasyon sadece birkaç durumda değerlidir:

  • useMemo’ya koyduğunuz hesaplama fark edilir derecede yavaştır ve bağımlılıkları nadiren değişiyordur.
  • Bunu memo içine sarılmış bir bileşene prop olarak geçersiniz. Değer değişmediyse yeniden render işlemini atlamak istersiniz. Memoization, bileşeninizin yalnızca bağımlılıklar aynı olmadığında yeniden render’lanmasını sağlar.
  • Geçtiğiniz değer daha sonra bazı Hook’ların bağımlılığı olarak kullanılır. Örneğin, belki başka bir useMemo hesaplama değeri bu değere bağlıdır. Ya da bu değere useEffect ile bağlısınızdır.

Diğer durumlarda bir hesaplamayı useMemo içine sarmanın hiçbir faydası yoktur. Bunu yapmanın önemli bir zararı da yoktur, bu nedenle bazı ekipler her durumu düşünmemeyi ve mümkün olduğunca çok belleğe almayı tercih eder. Bu yaklaşımın dezavantajı, kodun daha az okunabilir hale gelmesidir. Ayrıca, her belleğe alma işlemi etkili değildir: “her zaman yeni” olan tek bir değer, tüm bir bileşen için memoizasyonu kırmak için yeterlidir.

Pratikte, birkaç ilkeyi izleyerek çok sayıda belleğe alma işlemini gereksiz hale getirebilirsiniz:

  1. Bir bileşen diğer bileşenleri görsel olarak sardığında, JSX’i alt bileşen olarak kabul etmesine izin verin. Bu sayede, kapsayıcı bileşen kendi state’ini güncellediğinde, React alt bileşenlerinin yeniden render edilmesine gerek olmadığını bilir.
  2. Yerel state’i tercih edin ve state’i gereğinden daha yukarı kaldırmayın. Örneğin, formlar gibi geçici state’leri ve bir öğenin üzerine gelindiğinde ağacınızın tepesinde veya global bir state kütüphanesinde mi olduğunu saklamayın.
  3. Render mantığınızı saf tutun. Bir bileşenin yeniden render edilmesi bir soruna neden oluyorsa veya göze çarpan bir görsel yapaylık oluşturuyorsa, bileşeninizde bir hata var demektir! Memoizasyon eklemek yerine hatayı düzeltin.
  4. State’i güncelleyen gereksiz Efektlerden kaçının. React uygulamalarındaki performans sorunlarının çoğu, bileşenlerinizin tekrar tekrar render edilmesine neden olan Efektlerden kaynaklanan güncelleme zincirlerinden kaynaklanır. 1.Efektlerinizden gereksiz bağımlılıkları kaldırmaya çalışın Örneğin, memoization yerine, bir nesneyi veya bir işlevi bir Efektin içine veya bileşenin dışına taşımak genellikle daha basittir.

Belirli bir etkileşim hala gecikmeli geliyorsa, React Developer Tools profilleyicisini kullanın ve hangi bileşenlerin memoizasyondan en çok yararlanacağını görün ve gerektiğinde memoizasyon ekleyin. Bu ilkeler bileşenlerinizin hata ayıklamasını ve anlaşılmasını kolaylaştırır, bu nedenle her durumda bunları takip etmek iyidir. Uzun vadede, bunu kesin olarak çözmek için otomatik olarak granüler memoization yapmayı araştırıyoruz.

useMemo ile bir değeri doğrudan hesaplama arasındaki fark

Örnek 1 / 2:
useMemo ile yeniden hesaplamayı atlama

Bu örnekte, filterTodos uygulaması yapay olarak yavaşlatılmıştır, böylece işleme sırasında çağırdığınız bazı JavaScript işlevleri gerçekten yavaş olduğunda ne olduğunu görebilirsiniz. Sekmeleri değiştirmeyi ve temayı değiştirmeyi deneyin.

Sekmeleri değiştirmek yavaş hissettirir çünkü yavaşlatılmış filterTodosu yeniden çalıştırmaya zorlar. Bu beklenen bir durumdur çünkü tab değişmiştir ve bu nedenle tüm hesaplamanın yeniden çalıştırılması gerekir. (Neden iki kez çalıştığını merak ediyorsanız, burada açıklanmıştır.)

Temayı değiştir. Yapay yavaşlamaya rağmen useMemo sayesinde hızlıdır. Yavaş filterTodos çağrısı atlandı çünkü hem todos hem de tab (useMemoya bağımlılık olarak ilettiğiniz değişkenler) son render’dan bu yana değişmedi.

import { useMemo } from 'react';
import { filterTodos } from './utils.js'

export default function TodoList({ todos, theme, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  return (
    <div className={theme}>
      <p><b>Note: <code>filterTodos</code> is artificially slowed down!</b></p>
      <ul>
        {visibleTodos.map(todo => (
          <li key={todo.id}>
            {todo.completed ?
              <s>{todo.text}</s> :
              todo.text
            }
          </li>
        ))}
      </ul>
    </div>
  );
}


Bileşenlerin yeniden oluşturulmasını atlama

Bazı durumlarda, useMemo alt bileşenleri yeniden oluşturma performansını optimize etmenize de yardımcı olabilir. Bunu göstermek için, bu TodoList bileşeninin visibleTodos öğesini alt bileşen List öğesine bir prop olarak aktardığını varsayalım:

export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}

Tema prop’unu değiştirmenin uygulamayı bir anlığına dondurduğunu fark ettiniz, ancak JSX’inizden <List /> öğesini kaldırırsanız, hızlı hissedersiniz. Bu size List bileşenini optimize etmeyi denemeye değer olduğunu söyler.

Varsayılan olarak, bir bileşen yeniden render edildiğinde, React tüm alt bileşenlerini özyinelemeli olarak yeniden render eder. Bu nedenle, TodoList farklı bir tema ile yeniden oluşturulduğunda, List bileşeni de aynı şekilde yeniden oluşturulur. Bu, yeniden render için fazla hesaplama gerektirmeyen bileşenler için uygundur. Ancak, yeniden render işleminin yavaş olduğunu doğruladıysanız, Liste, prop’lar son render ile aynı olduğunda memo içine sararak yeniden oluşturmayı atlamasını söyleyebilirsiniz:

import { memo } from 'react';

const List = memo(function List({ items }) {
// ...
});

Bu değişiklikle List, tüm prop’lar son render işlemindeki ile aynı ise yeniden render işlemini atlayacaktır. İşte bu noktada hesaplamayı önbelleğe almak önemli hale geliyor! visibleTodosu useMemo olmadan hesapladığınızı düşünün:

export default function TodoList({ todos, tab, theme }) {
// Tema her değiştiğinde, bu farklı bir dizi olacak...
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
{/* ... böylece List'in prop'ları asla aynı olmayacak ve her seferinde yeniden oluşturulacaktır */}
<List items={visibleTodos} />
</div>
);
}

Yukarıdaki örnekte, {} nesne literali’nin her zaman yeni bir nesne oluşturmasına benzer şekilde, filterTodos fonksiyonu her zaman farklı bir dizi oluşturur. Normalde bu bir sorun teşkil etmez, ancak List prop’larının asla aynı olmayacağı ve memo optimizasyonunuzun çalışmayacağı anlamına gelir. İşte bu noktada useMemo kullanışlı hale gelir:

export default function TodoList({ todos, tab, theme }) {
// React'e yeniden oluşturmalar arasında hesaplamanızı önbelleğe almasını söyleyin...
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // ...yani bu bağımlılıklar değişmediği sürece...
);
return (
<div className={theme}>
{/* ...List, aynı prop'ları alacak ve yeniden oluşturmayı atlayabilecektir */}
<List items={visibleTodos} />
</div>
);
}

visibleTodos hesaplamasını useMemo içine sararak, yeniden render’lar arasında (bağımlılıklar değişene kadar) aynı değere sahip olmasını sağlarsınız. Belirli bir nedenle yapmadığınız sürece bir hesaplamayı useMemo içine sarmak zorunda değilsiniz. Bu örnekteki nedeni memo, içine sarılmış bir bileşene aktarmanız ve bunun yeniden oluşturmayı (render’ı) atlamasına izin vermesidir. Bu sayfada useMemo’yu kullanmak için birkaç neden daha anlatılmaktadır.

Derinlemesine İnceleme

Bireysel JSX node’larını memoize etme

List’i memo içine sarmak yerine, <List /> JSX node’unu kendisini useMemo içine sarabilirsiniz:

export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
const children = useMemo(() => <List items={visibleTodos} />, [visibleTodos]);
return (
<div className={theme}>
{children}
</div>
);
}

Davranış aynı olacaktır. Eğer visibleTodos değişmediyse, List yeniden oluşturulmayacaktır.

<List items={visibleTodos} /> gibi bir JSX node’u { type: List, props: { items: visibleTodos } } gibi bir nesnedir. Bu nesneyi oluşturmanın maliyeti çok ucuzdur, ancak React, içeriğin son seferle aynı olup olmadığını bilmez. Bu yüzden React varsayılan olarak List bileşenini yeniden oluşturacaktır.

Ancak, React önceki render sırasında aynı JSX’i görürse, bileşeninizi yeniden render etmeye çalışmaz. Bunun nedeni JSX node’larının değişmez (immutable) olmasıdır. Bir JSX node nesnesi zaman içinde değişmemiş olabilir, bu nedenle React yeniden oluşturmayı atlamanın güvenli olduğunu bilir. Ancak bunun işe yaraması için node’un yalnızca kodda aynı görünmesi değil, gerçekte aynı nesne olması gerekir. Bu örnekte useMemo’nun yaptığı şey budur.

JSX node’larını useMemo içine manuel olarak sarmak uygun değildir. Örneğin, bunu koşullu olarak yapamazsınız. Genellikle bu nedenle bileşenleri JSX node’larını sarmak yerine memo ile sararsınız.

Yeniden oluşturmayı atlamak ile her zaman yeniden oluşturmak arasındaki fark

Örnek 1 / 2:
useMemo ve memo ile yeniden oluşturmayı atlama

Bu örnekte, List bileşeni yapay olarak yavaşlatılmıştır, böylece render’ladığınız bir React bileşeni gerçekten yavaş olduğunda ne olduğunu görebilirsiniz. Sekmeleri değiştirmeyi ve temayı değiştirmeyi deneyin.

Sekmeleri değiştirmek yavaş hissettiriyor çünkü yavaşlatılmış List bileşinini yeniden oluşturmaya zorluyor. Bu beklenen bir durumdur çünkü tab değişmiştir ve bu nedenle kullanıcının yeni seçimini ekrana yansıtmanız gerekir.

Şimdi, temayı değiştirmeyi deneyin. Bu işlem useMemo ve memo sayesinde, yapay yavaşlamaya rağmen, hızlıdır! List yeniden render edilmeyi atladı çünkü visibleItems dizisi son render işleminden bu yana değişmedi. useMemoya bağımlılık olarak aktardığınız hem todos hem de tab son render işleminden bu yana değişmediği için visibleItems dizisi değişmedi.

import { useMemo } from 'react';
import List from './List.js';
import { filterTodos } from './utils.js'

export default function TodoList({ todos, theme, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  return (
    <div className={theme}>
      <p><b>Note: <code>List</code> is artificially slowed down!</b></p>
      <List items={visibleTodos} />
    </div>
  );
}


Başka bir Hook’un bağımlılığını memoize etme

Doğrudan bileşen gövdesinde oluşturulan bir nesneye bağlı olan bir hesaplamanız olduğunu varsayalım:

function Dropdown({ allItems, text }) {
const searchOptions = { matchMode: 'whole-word', text };

const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 Dikkat: Bileşen gövdesinde oluşturulan bir nesneye bağımlılık
// ...

Bu şekilde bir nesneye bağlı olmak, belleğe alma (memoizasyon) işleminin amacını ortadan kaldırır. Bir bileşen yeniden oluşturulduğunda, doğrudan bileşen gövdesinin içindeki tüm kod yeniden çalışır. SearchOptions nesnesini oluşturan kod satırları da her yeniden oluşturmada çalışacaktır. searchOptions, useMemo çağrınızın bir bağımlılığı olduğundan ve her seferinde farklı olduğundan, React bağımlılıkların farklı olduğunu bilir ve searchItemsı her seferinde yeniden hesaplar.

Bunu düzeltmek için, searchOptions nesnesini bir bağımlılık olarak geçirmeden önce kendisini memoize edebilirsiniz:

function Dropdown({ allItems, text }) {
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // ✅ Yalnızca text değiştiğinde değişir

const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ✅ Yalnızca allItems veya searchOptions değiştiğinde değişir
// ...

Yukarıdaki örnekte, text değişmediyse, searchOptions nesnesi de değişmeyecektir. Ancak, daha da iyi bir çözüm searchOptions nesne bildirimini useMemo hesaplama fonksiyonunun içine taşımaktır:

function Dropdown({ allItems, text }) {
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, text]); // ✅ Yalnızca allItems veya text değiştiğinde değişir
// ...

Şimdi hesaplamanız doğrudan text’e bağlıdır (bu bir string’dir ve “yanlışlıkla” farklı hale gelemez).


Bir fonksiyonu memoize etme

Form bileşeninin memo içine sarıldığını varsayalım. Bir fonksiyonu prop olarak iletmek istiyorsunuz:

export default function ProductPage({ productId, referrer }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}

return <Form onSubmit={handleSubmit} />;
}

Nasıl {} farklı bir nesne yaratıyorsa, function() {} gibi fonksiyon bildirimleri ve () => {} gibi ifadeler de her yeniden oluşturmada farklı bir fonksiyon üretir. Yeni bir fonksiyon oluşturmak tek başına bir sorun değildir. Bu kaçınılması gereken bir şey değildir! Ancak, Form bileşeni memoize edilmişse, muhtemelen hiçbir prop değişmediğinde yeniden oluşturmayı atlamak istersiniz. Her zaman farklı olan bir prop, memoizasyonun amacını ortadan kaldıracaktır.

Bir fonksiyonu useMemo ile memoize etmek için, hesaplama fonksiyonunuzun başka bir fonksiyon döndürmesi gerekir:

export default function Page({ productId, referrer }) {
const handleSubmit = useMemo(() => {
return (orderDetails) => {
post('/product/' + product.id + '/buy', {
referrer,
orderDetails
});
};
}, [productId, referrer]);

return <Form onSubmit={handleSubmit} />;
}

Bu hantal görünüyor! Fonksiyonları memoize etmek o kadar yaygındır ki, React özellikle bunun için yerleşik bir Hook’a sahiptir. Ekstra iç içe fonksiyon yazmak zorunda kalmamak için fonksiyonlarınızı useMemo yerine useCallback içine sarın:

export default function Page({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + product.id + '/buy', {
referrer,
orderDetails
});
}, [productId, referrer]);

return <Form onSubmit={handleSubmit} />;
}

Yukarıdaki iki örnek tamamen eşdeğerdir. useCallbackin tek faydası, fazladan bir iç içe fonksiyon yazmaktan kaçınmanızı sağlamasıdır. Başka bir şey yapmaz. useCallback hakkında daha fazla bilgi edinin.


Sorun giderme

Hesaplamam her yeniden oluşturmada iki kez çalışıyor

[Strict Mod]‘da (/reference/react/StrictMode), React bazı fonksiyonlarınızı bir yerine iki kez çağıracaktır:

function TodoList({ todos, tab }) {
// Bu bileşen fonksiyonu her render için iki kez çalışacaktır.

const visibleTodos = useMemo(() => {
// Bağımlılıklardan herhangi biri değişirse bu hesaplama iki kez çalışacaktır.
return filterTodos(todos, tab);
}, [todos, tab]);

// ...

Bu beklenen bir durumdur ve kodunuzu bozmamalıdır.

Bu sadece geliştirme amaçlı davranış, bileşenleri saf tutmanıza yardımcı olur. React, çağrılardan birinin sonucunu kullanır ve diğer çağrının sonucunu yok sayar. Bileşeniniz ve hesaplama işlevleriniz saf olduğu sürece, bu durum mantığınızı etkilememelidir. Ancak, saf olmadıkları takdirde, bu durum hatayı fark etmenize ve düzeltmenize yardımcı olur.

Örneğin, bu saf olmayan hesaplama fonksiyonu, prop olarak aldığınız bir diziyi mutasyona uğratır:

const visibleTodos = useMemo(() => {
// 🚩 Hata: bir prop'u mutasyona uğratmak
todos.push({ id: 'last', text: 'Go for a walk!' });
const filtered = filterTodos(todos, tab);
return filtered;
}, [todos, tab]);

React fonksiyonunuzu iki kez çağırır, böylece todo’nun iki kez eklendiğini fark edersiniz. Hesaplamanız mevcut nesneleri değiştirmemelidir, ancak hesaplama sırasında oluşturduğunuz yeni nesneleri değiştirmenizde bir sakınca yoktur. Örneğin, filterTodos fonksiyonu her zaman farklı bir dizi döndürüyorsa, bunun yerine döndürülen diziyi değiştirebilirsiniz:

const visibleTodos = useMemo(() => {
const filtered = filterTodos(todos, tab);
// ✅ Doğru: hesaplama sırasında oluşturduğunuz bir nesnenin mutasyona uğratılması
filtered.push({ id: 'last', text: 'Go for a walk!' });
return filtered;
}, [todos, tab]);

Saflık hakkında daha fazla bilgi edinmek için keeping components pure bölümünü okuyun.

Ayrıca, mutasyon olmadan nesneleri güncelleme ve dizileri güncelleme kılavuzlarına göz atın.


Benim useMemo çağrımın bir nesne döndürmesi gerekiyor, ancak undefined döndürüyor

Buradaki kod çalışmıyor:

// 🔴 () => { ile bir ok fonksiyonundan bir nesne döndüremezsiniz
const searchOptions = useMemo(() => {
matchMode: 'whole-word',
text: text
}, [text]);

JavaScript’te, () => { ok fonksiyonu gövdesini başlatır, bu nedenle { ayracı nesnenizin bir parçası değildir. Bu yüzden bir nesne döndürmez ve hatalara yol açar. Bunu ({ ve }) gibi parantezler ekleyerek düzeltebilirsiniz:

// Bu işe yarar, ancak birinin tekrar bozması kolaydır
const searchOptions = useMemo(() => ({
matchMode: 'whole-word',
text: text
}), [text]);

Ancak, bu yine de kafa karıştırıcıdır ve birinin parantezleri kaldırarak bozması çok kolaydır.

Bu hatadan kaçınmak için, açık bir şekilde return deyimi yazın:

// ✅ Bu işe yarar ve açıktır
const searchOptions = useMemo(() => {
return {
matchMode: 'whole-word',
text: text
};
}, [text]);

Bileşenim her render olduğunda, useMemo içindeki hesaplama yeniden çalışıyor

Bağımlılık dizisini ikinci bir bağımsız değişken olarak belirttiğinizden emin olun!

Bağımlılık dizisini unutursanız, useMemo her seferinde hesaplamayı yeniden çalıştıracaktır:

function TodoList({ todos, tab }) {
// 🔴 Her seferinde yeniden hesaplar: bağımlılık dizisi yok
const visibleTodos = useMemo(() => filterTodos(todos, tab));
// ...

Bu da, bağımlılık dizisini ikinci bir bağımsız değişken olarak geçiren düzeltilmiş versiyonudur:

function TodoList({ todos, tab }) {
// ✅ Gereksiz yere yeniden hesaplama yapmaz
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...

Bu işe yaramazsa, sorun bağımlılıklarınızdan en az birinin önceki render işleminden farklı olmasıdır. Bağımlılıklarınızı konsola manuel olarak kaydederek bu sorunu ayıklayabilirsiniz:

const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
console.log([todos, tab]);

Daha sonra konsolda farklı yeniden oluşturmalardan dizilere sağ tıklayabilir ve her ikisi için de “Genel değişken olarak sakla”yı seçebilirsiniz. İlkinin temp1 ve ikincisinin temp2 olarak kaydedildiğini varsayarsak, her iki dizideki her bir bağımlılığın aynı olup olmadığını kontrol etmek için tarayıcı konsolunu kullanabilirsiniz:

Object.is(temp1[0], temp2[0]); // İlk bağımlılık, diziler arasında aynı mı?
Object.is(temp1[1], temp2[1]); // İkinci bağımlılık, diziler arasında aynı mı?
Object.is(temp1[2], temp2[2]); // ... ve her bağımlılık için böyle devam eder ...

Hangi bağımlılığın memoizasyonu bozduğunu bulduğunuzda, ya onu kaldırmanın bir yolunu bulun ya da onu da memoize edin.


Bir döngü içinde her liste öğesi için useMemo çağırmam gerekiyor, ancak buna izin verilmiyor

Diyelim ki Chart bileşeni memo içine sarılmış olsun. ReportList bileşeni yeniden oluşturulduğunda listedeki her Chart’ın yeniden oluşturulmasını atlamak istiyorsunuz. Ancak, useMemo öğesini bir döngü içinde çağıramazsınız:

function ReportList({ items }) {
return (
<article>
{items.map(item => {
// 🔴 useMemo'yu bu şekilde bir döngü içinde çağıramazsınız:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure key={item.id}>
<Chart data={data} />
</figure>
);
})}
</article>
);
}

Bunun yerine, her bir öğe için bir bileşen çıkarın ve tek tek öğeler için verileri not edin:

function ReportList({ items }) {
return (
<article>
{items.map(item =>
<Report key={item.id} item={item} />
)}
</article>
);
}

function Report({ item }) {
// ✅ En üst seviyede useMemo'yu çağırın:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure>
<Chart data={data} />
</figure>
);
}

Alternatif olarak, useMemo seçeneğini kaldırabilir ve bunun yerine Report seçeneğinin kendisini memo ile sarabilirsiniz. Eğer item prop’u değişmezse, Report yeniden oluşturmayı atlayacaktır, dolayısıyla Chart da yeniden oluşturmayı atlayacaktır:

function ReportList({ items }) {
// ...
}

const Report = memo(function Report({ item }) {
const data = calculateReport(item);
return (
<figure>
<Chart data={data} />
</figure>
);
});