React Neden Tekrar Render İşlemi Yapar?

Gizem Korkmaz
10 min readAug 22, 2022

--

Bu yazı Josh Comeau’nun Why React Re-Renders adlı makalesinin bir çevirisidir.

Dürüst olacağım. React’in tekrar render etme sürecinin nasıl işlediğini tam olarak kavrayamadan yıllarca profesyonel olarak React ile çalıştım. 😅

Sanıyorum ki bu durum pek çok React geliştiricisi için de geçerli. Bizi ileriye götürecek kadarını anlıyoruz ancak bir grup React geliştiricisine “React’te tekrar render edilmeyi tetikleyen şey nedir?” tarzı sorular sorulduğunda muhtemelen baştan savma bir sürü farklı cevaplar alırsınız.

Bu konu hakkında birçok yanlış fikir mevcut ve bu durum bazı belirsizliklere sebep olabiliyor. Eğer React’in render sürecini anlayamazsak React.memo'yu nasıl kullanacağız veya fonksiyonlarımızı ne zaman useCallback içerisine alacağız?

Bu öğretici yazıda React’in ne zaman ve neden tekrar render işlemini gerçekleştirdiğine dair zihinsel bir model inşa edeceğiz. Aynı zamanda React devtools’u kullanarak belirli bir bileşenin neden tekrar render olduğunu açıklayabilmeyi de öğreneceğiz.

Yazının hedef kitlesi

Bu yazı başlangıç-orta seviye React geliştiricilerine, React ile kendilerini daha rahat hissetmelerine yardım etmek amacıyla yazılmıştır. Henüz React ile ilk adımlarınızı atıyorsanız bunu yazıyı yer imlerinize eklemeyi ve birkaç hafta sonra geri gelmeyi tercih edebilirsiniz!

React Döngüsünün Özü

O halde en temel gerçekten başlayalım: React’te tekrar render işlemlerinin hepsi bir state değişimi ile başlar. Bu, React’in bir bileşeni tekrar render etmesi için tek “tetiklenme” aracıdır.

Şimdi, bu kulağa çok da doğru gelmiyor muhtemelen… Sonuçta bileşenler props değiştikçe tekrar render edilmiyorlar mıydı? Peki ya context ne olacak?

Olay şu ki, bir bileşen tekrar render edildiğinde kapsadığı diğer tüm bileşenler de tekrar render olur.

Şu örneğe bir bakalım:

Bu örnekte 3 bileşenimiz var: BigCountNumber'u render eden Counter ve en tepede Counter'ı render eden App.

React’te her bir state değişkeni, belirli bir bileşen örneğine (component instance) bağlıdır. Bu örnekte, Counter bileşenine bağlı, tek bir state parçası olan count'a sahibiz.

State her değiştiğinde Counter tekrar render edilir. BigCountNumber da Counter tarafından render edildiği için, o da tekrar render edilecek.

İşte bu işlemi gösteren bir görsel. “Increment” butonuna tıklandığında state değişimi tetikleniyor:

Yeşil ışık bileşenin tekrar render edildiğini gösteriyor.

Harika, o zaman Büyük Yanılgılar #1'i ortadan kaldıralım: Bir state değişkeni değiştiğinde tüm app tekrar render edilir.

Biliyorum ki, bazı geliştiriciler React’in herhangi bir state değişiminde tüm uygulamayı kapsayan bir render işlemini zorunlu tuttuğuna inanıyor ancak bu doğru değil. Tekrar render edilmeler yalnızca o state’e sahip olan ve kapsadığı bileşenleri (eğer varsa) etkiler. Bu örnekte App bileşeni count state değişkeni değiştiğinde tekrar render edilmek zorunda değildir.

Bunu bir kural gibi ezberlemekten ziyade bir adım geriye gidelim ve bunun neden bu şekilde işlediğini anlamaya çalışalım.

React’in “asıl işi” uygulama arayüzü ile React state’ini senkron halde tutmaktır. Tekrar render edilmelerinin amacı neyin değişmesi gerektiğini anlayabilmektir.

Yukarıdaki “Counter” örneğini ele alalım. Uygulama ilk mount olduğunda React tüm bileşenleri render eder ve DOM’un ne şekilde gözükmesi gerektiği konusunda şöyle bir şey ortaya çıkarır:

Her bir render, uygulamanın mevcut state’ine göre arayüzün nasıl gözükmesi gerektiğini gösteren ve tıpkı bir fotoğraf makinesi tarafından çekilen anlık bir görüntü gibidir.

React, iki görüntü arasında neler değiştiğini görebilmek için “aradaki farkı bulun” oyunu oynar. Bu durumda, paragrafımızın 0dan 1'e değişen bir text node’u olduğunu görür ve görüntü ile eşleşmesi için bu text node’unu günceller. Yaptığı işin tamamlanmasından memnun bir halde bir sonraki state değişimini beklemeye başlar.

Bu, React döngüsünün özüdür.

Aklımızdaki bu görüntü ile şimdi render görselimize tekrar bakalım:

count state’imiz Counter bileşeni ile alakalıdır. React uygulamalarında data akışı “yukarı doğru” olmadığı için bu state’in hiçbir şekilde <App />'i etkileyemeyeceğini biliyoruz. Bu yüzden o bileşeni tekrar render etmemize gerek kalmıyor.

Ancak Counter'ın child’ı olan BigCountNumber'ı tekrar render etmemiz gerekiyor. count state’ini gösteren asıl bileşen bu. Bunu tekrar render etmezsek, paragrafımızdaki text node’unun 0'dan 1'e değişmesi gerektiğini bilemeyiz. Bu yüzden, bu bileşeni de görüntümüze dahil ediyoruz.

Tekrar render edilmenin amacı state’in arayüze nasıl etki ettiğini anlayabilmektir. Böylece doğru görüntüyü elde edebilmek için tüm potansiyel etkilenecek bileşenleri tekrar render etmemiz gerekir.

Propsla bir alakası yok

O halde şimdi Büyük Yanılgılar #2'den bahsedelim: Props değiştiği için bileşen tekrar render edilir.

Bunu güncellenmiş bir örnekle inceleyelim.

Aşağıdaki kodda “Counter” uygulamamıza yeni bir bileşen eklendi, Decoration:

(Tüm bileşenleri tek bir dosya içerisinde tutmak biraz kalabalık olmaya başlamıştı, bu yüzden insiyatif alarak yapıyı tekrar düzenledim. Decoration bileşeni dışında genel yapı aynı.)

Counter’ımız artık köşede Decoration bileşeninden gelen, minik sevimli bir yelken görseli içeriyor. Eğer bu count'a bağlı değilse, o zaman count değiştiğinde o da değişmeyecektir, değil mi?

Yani… Pek öyle değil.

Bir bileşen tekrar render edildiğinde, belirli bir state değişkenini props ile gönderip göndermediğinden bağımsız olarak kapsadığı tüm bileşenleri tekrar render etmeye çalışır.

Bu genel kanının aksi gibi duruyor… count'ı prop olarak <Decoration />'a göndermiyorsak neden tekrar render edilmeye ihtiyaç duyuyor?

Cevabı şu: React’in <Decoration>'ın doğrudan ya da dolaylı olarak count state değişkenine bağlı olduğunu %100 emin olarak bilmesi pek mümkün değil.

İdeal bir dünyada, React bileşenleri her zaman “saf (pure)” olmalıdır. Bir saf bileşen aynı props’u aldığında her zaman aynı arayüzü oluşturur.

Gerçek dünyada ise bileşenlerimizin birçoğu saf değildir. Saf olmayan bir bileşen oluşturmak şaşırtıcı derecede kolaydır.

Bu bileşen, o anki zamana bağlı olduğu için her render edildiğinde farklı bir değer gösterecektir!

Bunun bir başka gizli versiyonu ise refler ile kullanımdır. Bir ref’i prop olarak gönderirsek React bunun son render’da mutate edip etmediğimizden emin olamayacaktır. Bu sebeple, tedbir amaçlı tekrar render etmeyi tercih edecektir.

React’in ilk görevi application state ile arayüzde kullanıcının gördüğü state’in senkronize olduğundan emin olmaktır. Bu sebeple, render işlemleri gereğinden fazla yaparak riske girmez. Kullanıcıya eski arayüzü göstermekten kaçınır.

O halde yanılgımıza tekrar dönelim: Props’un tekrar render edilmelerle bir ilgisi yoktur. <BigCountNumber> bileşenimiz, count prop’u değiştiği için tekrar render edilmez.

Bir bileşen tekrar render edildiğinde, state değişkenlerinin güncellenmesi sebebiyle, bu yeniden render edilme işlemi React’in yeni görseldeki boşlukları doldurabilmesi ve yeni anlık görüntüyü yakalaması için ağacın en alt noktasına kadar süzülür.

Bu standart çalışma sürecidir ancak bunun üzerinde ufak değişiklikler yapmak mümkündür.

Saf bileşenler oluşturmak

React.memo ya da React.PureComponent class bileşenlerine aşina olabilirsiniz. Bu iki araç bize belirli tekrar render edilme isteklerini görmezden gelmemize müsaade ederler.

Nasıl göründüğüne bir bakalım:

Decoration bileşenimizi React.memo ile sararak React’e “Hey, bu bileşenin saf olduğunu biliyorum. Props’u değişmediği sürece onu tekrar render etmene gerek yok.” demiş oluyoruz.

Bu, memoization olarak bilinen bir tekniği kullanır.

Kelime “r” harfi içermez ancak bunu bir tür “memorization (ezberleme)” olarak düşününebiliriz. Özünde, React’in bir önceki anlık görüntüyü hatırlamasıdır. Eğer herhangi bir props değişmediyse, React yeni bir tanesini oluşturmakla uğraşmayıp eski görüntüyü kullanır.

BigCountNumber ile DecorationReact.memo helper’ı ile sardığımızı düşünelim. Tekrar render edilmeye şöyle bir etkisi olacak:

count değiştiğinde Counter'ı tekrar render ediyoruz ve bu yüzden React kapsadığı iki bileşeni de render etmeye çalışacak.

BigCountNumber, count'ı prop olarak aldığı için bu prop değiştiğinde BigCountNumber tekrar render edildi. Ancak Decoration props’u değişmediği için (hiç props’u yok) orijinal anlık görüntüyü kullanıldı.

React.memo'yu tembel bir fotoğrafçı gibi düşünmek hoşuma gidiyor. Eğer ondan aynı şeyin 5 fotoğrafını çekmesini isterseniz 1 tane çeker ve size o fotoğrafın 5 kopyasını verir. Fotoğrafçı ancak istekleriniz değiştiği zaman yeni bir fotoğraf çekecektir.

Eğer kurcalamak isterseniz bunun canlı kod versiyonu şu şekilde. Her memoized edilmiş bileşenin içerisine birer console.info eklendi, bu sayede tam olarak hangi bileşenin render edildiğini görebilirsiniz.

Bunun neden varsayılan davranış olmadığını merak ediyor olabilirsiniz. Sonuçta çoğu zaman istediğimiz şey bu değil mi? Render edilmemesi gereken bileşenleri render etmeyi atladığımızda performansı arttırmış oluruz?

Sanıyorum ki geliştiriciler olarak tekrar render edilme işlemlerinin ne kadar maliyetli olduğunu abartma eğilimindeyiz. Mevcut durumdaki Decoration bileşenimizde tekrar render edilmeler şimşek hızında.

Eğer bir bileşenin birçok props’u varsa ve çok fazla bileşeni kapsamıyorsa o zaman herhangi bir props’un değişip değişmediğini kontrol etmek aslında tekrar o bileşeni render etmekten çok daha yavaş olabilir.

Ve bu yüzden oluşturduğumuz her bir bileşeni memoize etmek aksi bir etki yaratabilir. React, bu anlık görüntüleri çok hızlı yakalaması için tasarlandı! Ancak belirli durumlarda, içerisinde çok fazla bileşeni barındıran ya da içinde çok fazla iş yapılan bileşenler için bu oldukça faydalı olacaktır.

Bu gelecekte değişebilir!

React takımı, compile aşamasında “auto-memoize” yapısını çalıştırmanın mümkün olup olmadığını aktif olarak araştırıyor. Hala araştırma aşamasında, ancak erken deneyler umut verici görünüyor.

Daha fazlası için Xuan Huang’ın “React without memo” adlı şu konuşmasına bir göz atın.

Peki ya context?

Henüz context hakkında hiç konuşmadık, ama neyse ki, bu konuyu çok fazla karmaşıklaştırmıyor.

Varsayılan olarak, bir bileşenin alt öğeleri o bileşenin state’i değişirse tekrar render edilir. Dolayısıyla kapsanan bileşenlere context ile bu state’i göndersek de herhangi bir şeyi değiştirmeyecek; çünkü her durumda bu bileşenler zaten tekrar render edilmiş olacak!

Saf bileşenler açısından düşünüldüğünde context bir çeşit “görünmeyen props” ya da “iç props” gibidir.

Şu örneğe bir göz atın. Burada UserContext` context’ini consume eden saf bir bileşen var:

Canlı halini kurcalayın:

Bu durumun, saf bileşenin yalnızcaReact.useContext hook’u ile context’i consume ederse gerçekleşeceğini unutmayın. Context’in, onu consume etmeye çalışmayan saf bileşenleri bozması konusunda endişelenmenize gerek yok.

React Devtools ile Profiler incelemesi

Bir süre React ile çalıştıysanız, belirli bir bileşenin neden yeniden oluşturulduğunu anlamaya çalışmak gibi sinir bozucu bir deneyim yaşamışsınızdır. Gerçek hayatta çoğu zaman bunun sebebi hiç de belirli değildir! Neyse ki, React Devtool bu konuda yardımcı olabilir.

Öncelikle React Devtools’u tarayıcınıza eklenti olarak indirmeniz gerekiyor. Şimdilik Chrome ve Firefox için kullanılabilir. Bu yazının amaçları doğrultusunda, talimatlar çok fazla değişmeyecek olsa da, Chrome kullandığınızı varsayacağım.

Devtools’u Ctrl + Alt + I (veya MacOS’ta ⌘ + Option + I) ile açın. İki yeni sekmenin açıldığını görmelisiniz:

“Profiler” kısmı ile ilgileniyoruz. Bu tab’ı seçin.

Genel akış şu şekilde görünüyor olacak:

  1. Küçük mavi “record” yuvarlağına tıklayarak kaydı başlatın.
  2. Uygulamanızda birkaç işlem yapın.
  3. Kaydetmeyi durdurun.
  4. Neler olduğunu daha iyi anlamak için kayda alınmış anlık görüntülere bakın.

Her bir render ayrı bir anlık görüntü olarak yakalanmış ve bunlar arasında oklar ile gezebiliyorsunuz. Bir bileşenin neden render edildiğine dair bilgiler sidebar’da görünüyor.

İlgilendiğiniz bileşene tıklayarak, belirli bir bileşenin neden yeniden render edildiğini tam olarak görebilirsiniz. Saf bir bileşen olması durumunda, bu güncellemeden hangi props’un sorumlu olduğunu da bize bildirecektir.

Bu aracı kişisel olarak çok sık kullanmıyorum ancak kullandığım zamanlarda tam bir cankurtaran oluyor!

Tekrar render edilmeleri vurgulamak

Ufak bir ipucu daha: React profiler’ın tekrar render edilen bileşenleri vurgulayabileceğiniz bir seçeneği var.

İşte söz konusu ayar:

Bu ayarı aktif hale getirdiğinizde tekrar render edilen bileşenlerin etrafında yanıp sönen yeşil dikdörtgenleri görebilirsiniz.

Bu, state güncellemelerinin tam olarak ne kadar kapsamlı olduğunu anlamamıza ve saf bileşenlerimizin tekrar render edilmelerinin başarılı bir şekilde önleyip önlemediğini test etmemize yardımcı olabilir!

Daha detaya inmek

Profiler’ı kullanmaya başladıktan sonra fark edeceğiniz şeylerden biri de, herhangi bir değişiklik yok gibi görünse de bazı saf bileşenler tekrar render edilecek!

React ile ilgili akıl almaz inceliklerden biri de bileşenlerin birer JavaScript fonksiyonları olmasıdır. Bir bileşeni render ettiğimizde aslında bir fonksiyon çağırıyoruz.

Bu, React bileşeni içerisinde tanımlanmış ne varsa hepsinin tekrar oluşturulduğu anlamına geliyor.

Hızlı bir örnek olması için şunu ele alalım:

Bu App` bileşenini her render edişimizde yeni bir obje oluşturuyoruz. Bu saf bileşenlerimize zarar verebilir; bu DogProfile` child’ı onu React.memo` içine alsak da almasak da tekrar render edilecek!

Birkaç hafta içinde bu blog yazısına bir “Bölüm II” yayınlayacağım. O zaman şu iki ünlü esrarengiz React hooklarını, useMemo` ile useCallback`'i biraz kurcalayacağız. Ve onları kullanmanın bu problemi nasıl çözdüğünü göreceğiz.

Ayrıca bir itirafım var: Bu eğitimler doğrudan, yakın zamanda çıkacak olan kursum “The Joy of React”ten alındı.

Neredeyse 7 yılı aşkın süredir React ile geliştirme yapıyorum ve onu nasıl etkili bir şekilde kullanacağımı öğrendim. React ile çalışmaya bayılıyorum; gün yüzüne çıkmış neredeyse tüm front-end framework’lerini denedim ancak hiçbiri beni React ile hissettiğim kadar üretken hissettirmedi.

“The Joy of React”te React’in nasıl çalıştığına dair zihinsel bir model oluşturacağız ve bu eğitimde olduğu gibi kavramları yakından inceleyeceğiz. Bu blogdaki gönderilerin aksine kurslarımda bunun gibi yazılı içeriklerin yanı sıra video içerikleri, alıştırmalar, etkileşimli keşifler ve bazı ufak oyunlar gibi “çoklu modalite” yaklaşımı kullanılıyor!

Önümüzdeki birkaç ay içerisinde kursum için yapacağım ön-lansman için sabırsızlanıyorum. Eğer ilgileniyorsanız kurs ana sayfasından güncellemeler için üye olabilirsiniz. 💖

Bonus: Performans İpuçları

React’te performans optimizasyonu geniş bir konu ve bunun hakkında kolayca birkaç blog yazısı çıkarabilirim. Umuyorum ki bu yazı, React performansı konusunda sağlam bir temel oluşturmanızda yardımcı olmuştur!

Bunla birlikte, React performans optimizasyonu hakkında öğrendiğim birkaç ipucunu paylaşacağım:

  • React Profiler, bir render’ın kaç milisaniyede gerçekleştiğini gösterir, ancak bu sayı güvenilir değildir. Genelde “development” moddayken profiler’ı kullanırız ve React “production” modundayken çok, çok daha hızlıdır. Uygulamanızın ne kadar performans gösterdiğini gerçekten anlamak için “Performance” tabını production uygulamasında kullanmalısınız. Bu size gerçek render sayılarını göstermenin yanı sıra layout/paint değişimlerini de gösterecektir.
  • Yüzde 90lık deneyimin nasıl olduğunu görmek için uygulamanızın alt seviye donanımlarda test etmenizi şiddetle tavsiye ederim. Örneğin ben, birkaç yıl önce Hindistan’da popüler olan uygun fiyatlı bir akıllı telefon olan Xiaomi Redmi 8'de periyodik olarak test yapıyorum. Bu deneyimi Twitter’da da paylaştım.
  • Lighthouse performans puanları, gerçek kullanıcı deneyiminin doğru bir yansıması değildir. Uygulamayı kullanmanın niteliksel deneyimine, herhangi bir otomatik aracın gösterdiği istatistiklerden çok daha fazla güveniyorum.
  • Birkaç yıl önce React Europe’da React performansı hakkında bir konuşma yaptım! Daha çok, birçok geliştiricinin ihmal ettiği bir alan olan “post-load” deneyimlerine odaklanıyor. Youtube’dan izleyebilirsiniz.
  • Aşırı optimize etmeyin! React profiler’ı öğrenirken render sayılarını olabildiğince azaltmak amacıyla optimizasyon çılgınlığına girmek cezbedicidir… Ama dürüst olmak gerekirse, React zaten en başından ezber bozacak düzeyde optimize edilmiş haldedir. Bu araçlar, işler biraz durgunlaşmaya başlarsa, bir performans sorununa yanıt olarak kullanıldığında en iyi sonucu verecektir.

--

--