useMemo
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
- Kullanım
- Sorun giderme
- Hesaplamam her yeniden oluşturmada iki kez çalışıyor
- Benim
useMemo
çağrımın bir nesne döndürmesi gerekiyor, ancak undefined döndürüyor - Bileşenim her render olduğunda,
useMemo
içindeki hesaplama yeniden çalışıyor - Bir döngü içinde her liste öğesi için
useMemo
çağırmam gerekiyor, ancak buna izin verilmiyor
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.
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:
() =>
gibi hiçbir argüman almayan ve hesaplamak istediğiniz şeyi döndüren bir hesaplama fonksiyonu.- 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.
Derinlemesine İnceleme
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
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ğereuseEffect
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:
- 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.
- 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.
- 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.
- 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.
Ö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ış filterTodos
u 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
(useMemo
ya 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, List
e, 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! visibleTodos
u 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
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.
Ö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. useMemo
ya 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> ); }
Preventing an Effect from firing too often
Sometimes, you might want to use a value inside an Effect:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const options = {
serverUrl: 'https://localhost:1234',
roomId: roomId
}
useEffect(() => {
const connection = createConnection(options);
connection.connect();
// ...
This creates a problem. Every reactive value must be declared as a dependency of your Effect. However, if you declare options
as a dependency, it will cause your Effect to constantly reconnect to the chat room:
useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // 🔴 Problem: This dependency changes on every render
// ...
To solve this, you can wrap the object you need to call from an Effect in useMemo
:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const options = useMemo(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ Only changes when roomId changes
useEffect(() => {
const options = createOptions();
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // ✅ Only changes when createOptions changes
// ...
This ensures that the options
object is the same between re-renders if useMemo
returns the cached object.
However, since useMemo
is performance optimization, not a semantic guarantee, React may throw away the cached value if there is a specific reason to do that. This will also cause the effect to re-fire, so it’s even better to remove the need for a function dependency by moving your object inside the Effect:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
const options = { // ✅ No need for useMemo or object dependencies!
serverUrl: 'https://localhost:1234',
roomId: roomId
}
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Only changes when roomId changes
// ...
Now your code is simpler and doesn’t need useMemo
. Learn more about removing Effect dependencies.
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. useCallback
in 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>
);
});