Front end Development

useMemo ve useCallback

July 18, 2022

İlk paylaşımım olan bu yazımda React’ın memoization hooklarından useMemo ve useCallback’i ele almak istedim. Birçok kişide kafa karışıklığına neden olan, nerede ve nasıl kullanılması gerektiğinden emin olunamayan bu hookları örnekleri ile birlikte açıklayacağım.

Eğer orta veya büyük ölçekli React projelerinde görev alıyorsanız bu hooklar ile büyük ihtimalle karşılaşmışsınızdır. Hatta tam olarak işlevlerini bilmeden projede kullanılıyor diye kullanmış da olabilirsiniz fakat bu kullanımların çoğunun gereksiz ve memory leak’lere neden olduğunu söylemem gerekir. Lafı daha fazla uzatmadan useMemo ile useCallback’ten ve asıl kullanım amaçlarından bahsedelim.

Memory Leak: Kodların kullanmış olduğu hafıza bloğu ile işinin sona ermesine rağmen o hafıza bloğunu bırakmamasına denir.

useMemo

https://medium.com/media/45631c873d51b7e4bf20d8d766a33b34

Örnekte de gözüktüğü şekilde useMemo, bir “create” fonksiyonu ile dependency array (bağımlı değişkenler dizisi) alır ve bir memoized value (hafızaya alınmış değer) dönüşü yapar. Örnekteki isimlendirmelerle devam edecek olursak memoizedValue değişkeni, dependency array içerisindeki a veya b değerleri değişmediği sürece aynı değeri hafızada tutacaktır. Eğer a veya b değerlerinden en az birisi değişirse “create” fonksiyonu çalışıp yeni bir değer dönüşü yapacaktır. Hesaplanması zaman alabilecek ve performans sorunlarına neden olacak değerleri hesaplamak için kullanılır (computationally expensive calculations).

useCallback

https://medium.com/media/93788bcc4bf279ec773c4cbc9eb10ef9

useCallback ise argüman olarak inline callback ile dependency array alır ve dönüş değeri olarak da memoized callback (hafızaya alınmış callback fonksiyonu) dönüşü yapar. useMemo’da da olduğu gibi memoizedCallback değişkeninin tutuğu callback sadece dependency array içerisindeki a veya b değişkenleri değiştiği zaman değişir. Ayrıca useCallback, gereksiz render etme işlemlerini önlemek için reference equality yöntemine göre optimize edilmiş alt componentlere callback fonksiyonu geçerken işimize yarar.

useMemo ve useCallback’in Kullanım Senaryoları

  1. Referential Equality (Referans Eşitliği)
  2. Computationally Expensive Calculations (Pahalı hesaplamalar)

useMemo ve useCallback gibi memoization hooklarını kullanmak maliyet doğurabilir. Bu yüzden gerçekten ihtiyaç duyulan noktalarda sorumluluk alınarak kullanılması gerekmektedir.

Referential Equality

JavaScript dilinde abstract equality (==) ve strict equality (===) olarak iki çeşit eşitlik operatörü vardır. React ise genellikle Object.is(object1, object2) eşitlik kontrolünü kullanır fakat çok detaya girmeden bu yapı ile strict equality’nin oldukça benzer olduğunu söyleyebiliriz.

https://medium.com/media/e941d2f9ec11787aa4e8402e916dd941

React’ta referential equalty’nin önemli olduğu 2 durum vardır. Bunlardan biri dependency array diğeri ise React.memo’dur.

Dependency Array

React’ta ki bazı hooklar, yapısında dependency array bulundurur. Bunlardan en bilindikleri ise useEffect, useMemo ve useCallback şeklinde sıralanabilir. useEffect üzerinden örnekler ile devam edecek olursak (örnekler anlaşılır olması için basit tutulmuştur):

https://medium.com/media/b1dde8d3cd329b325760200bd9d019f3

Yukarıdaki örnekte firstProp ile secondProp değiştiğinde useEffect’in tetiklenmesi amaçlanıp functionA’nın tekrar çalıştırılması beklenmektedir. Fakat JavaScript’in çalışma mantığı nedeniyle propObject her render sonrasında yenilenecektir. Bu nedenle de React, useEffect’in dependency array’inde bulunan değişkenin değiştiğini düşünüp firstProp ve secondProp değişmemiş olsa da useEffect’i çalıştıracaktır. Bu yüzden istenmeyen durumlar oluşabilmektedir ve iki farklı şekilde bu durumu düzeltmemiz mümkündür.

Bunlardan ilki ve mümkünse uygulanmasını tavsiye edeceğim çözüm yolu şu şekildedir.

https://medium.com/media/546645bb1e99a99023b6cdf8079834fb

Fakat bazı durumlarda geçtiğimiz proplar primitive (string, number vb.) olmayabilir. Yani function, object veya array gibi tiplere sahip olduklarında useMemo veya useCallback kullanarak yukarıda bahsettiğimiz sorunu düzeltebiliriz.

https://medium.com/media/564416a1800325f44cefdf11cb2c0727

Bu kullanım şekli de aslında useMemo ve useCallback hooklarının ortaya çıkış nedenini göstermektedir.

React.memo

Yine bir örnek üzerinden ilerleyecek olursak:

https://medium.com/media/5905d0fe1ce2324e413cef84dff4d98b

Örnekte de gözüktüğü üzere Counter komponentimiz, Button adlarında iki adet komponent barındırmaktadır. Button komponentlerinden birine her tıkladığımızda Counter komponenti içerisindeki statelerden biri değişeceği için her iki Button komponenti de render edilecektir. Tıklamış olduğumuz yani değerinin değişmesini istediğimiz Button komponentinin içerisinde güncel count değerine ulaşacağımız için bu komponentin render olması bize bir sorun teşkil etmezken diğer Button komponenti gereksiz bir şekilde tekrar render edilecektir. Bu sorunu düzeltmek için Button komponentini React.memo ile sarmalamamız gerekmektedir.

https://medium.com/media/2e5d7da8a70c2316c83f236f2168b3a2

React.memo(component, areEqual) aslında bir high order component’dir (üst katman bileşeni) ve gözüktüğü şekilde 2 adet argüman alır. İlk argüman sarmalamak istediğimiz komponent olurken ikinci argümanı ise kullanılması zorunlu olmayan özel karşılaştırma (areEqual) fonksiyonudur. Ayrıca React.memo normalde yüzeysel olarak (shallowly compare) komponent içerisindeki propları karşılaştırıp render işlemine karar verirken areEqual fonksiyonu sayesinde karşılaştırma işlemini özelleştirebiliriz. Detaylı bilgi için…

Yukarıdaki şekilde sarmaladıktan sonra Button komponenti React.memo sayesinde sadece propları değiştiği zaman tekrar render edilecektir. Tam da burada küçük bir noktayı daha düzeltmemiz gerekiyor çünkü Button komponentinin proplarından onClick’e verilen değişkenler non-primitive (function, object vb.) tipte olduğu için state değişiminden sonra yeniden oluşturulacaklar ve gereksiz render etme işlemini önleyememiş olacağız. Bu durumu düzeltmek için de devreye useCallback’i giriyor (duruma göre useMemo da kullanılabilir).

https://medium.com/media/09869b18093fcae2ea82088b9591c59f

Yaptığımız bu değişiklik ile gereksiz render etme işlemini önlemiş oluyor ve React.memo’yu sağlıklı bir şekilde kullanmış oluyoruz.

React.memo optimizasyonu bir maliyet doğurabileceğinden dolayı kullanımına karar vermeden önce emin olmalıyız ve doğru bir şekilde kullanmalıyız. Ayrıca React’ın hızlı çalışma yapısı sayesinde ciddi sorunlar çıkarmayan gereksiz render işlemleri göz ardı edilebilir.

Computationally Expensive Calculations

Maliyet doğurabilecek hesaplamaları useMemo içerisinde yapmak bize performans sağlayacaktır. useMemo’nun asıl ortaya çıkış amacı da bu tarz maliyetli işlemleri gereksiz tekrarlardan sakınmak içindir. Yine bir örnek verecek olursak (tam olarak kullanışlı bir senaryo olmasa da anlaşılır olmasını amaçladığım bir örnek):

https://medium.com/media/ca6adeeb98d64e74cf1e49a12fb4d43d

Yukarıdaki kod satırlarında gözüktüğü şekilde olası render işlemlerinde calculatePrimes tekrar tekrar çalışacaktır ve asal sayıların hesaplanmasının maliyet oluşturacağını varsayarsak projemize ciddi bir yük olacaktır.

https://medium.com/media/5675a0909096fff33745ccc773ac9b88

Bu şekilde useMemo ile sarmaladığımızda sadece dependency array içerisindeki değerlerin değişmesi durumda hesaplanacaktır. Yani gereksiz render işlemlerinde hesaplama yapmayıp hafızadaki hesaplanmış değeri döndürecektir.

Sonuç

useMemo ve useCallback hooklarının kullanımı performans sağlayabilirken maliyetinin de olabileceğini tekrar vurgulamak istiyorum. Özellikle aynı proje üzerinde çalıştığınız arkadaşlarınız için karışıklık doğurabilir ya da dependency array’de yapacağınız yanlışlık ile memoize edilmiş değerlerin garbage collector tarafından temizlenmesini engelleyerek performans sorunlarına neden olabilirsiniz. Eğer ölçümlerinizi ve kullanımınızı doğru yaparsanız başta da söylediğim gibi performans faydaları sağlayabilirsiniz.

Umarım faydalı bir yazı olmuştur 🙂

Referanslar

Author: Mehmet Mutlu

Tags

React useCallback useMemo