React'te Oluşturma Davranışını Anlama

Yayınlanan: 2020-11-16

Yaşam, ölüm, kader ve vergilerin yanı sıra, React'in render davranışı, hayattaki en büyük gerçeklerden ve gizemlerden biridir.

Hadi dalalım!

Herkes gibi ben de ön uç geliştirme yolculuğuma jQuery ile başladım. Saf JS tabanlı DOM manipülasyonu o zamanlar bir kabustu, bu yüzden herkesin yaptığı şey buydu. Sonra yavaş yavaş JavaScript tabanlı çerçeveler o kadar belirgin hale geldi ki artık onları görmezden gelemedim.

İlk öğrendiğim Vue oldu. İnanılmaz derecede zor zamanlar geçirdim çünkü bileşenler, durum ve diğer her şey tamamen yeni bir zihinsel modeldi ve her şeyi sığdırmak çok acı vericiydi. Ama sonunda yaptım ve sırtımı sıvazladım. Tebrikler dostum, dedim kendi kendime, dik bir tırmanış yaptın; Şimdi, çerçevelerin geri kalanı, onları öğrenmeniz gerekirse, çok kolay olacak.

Bir gün, React'i öğrenmeye başladığımda ne kadar yanıldığımı anladım. Facebook, Hooks'u fırlatıp herkese “Hey, bundan sonra bunu kullan. Ama sınıfları yeniden yazmayın; dersler iyi. Aslında çok değil ama olsun. Ancak Kancalar her şeydir ve onlar gelecek.

Anladım? Harika!".

Sonunda ben de o dağı geçtim. Ama sonra React'in kendisi kadar önemli ve zor bir şeyle karşılaştım: render .

Sürpriz!!!

React'te render ve onun gizemleriyle karşılaştıysanız, neden bahsettiğimi biliyorsunuzdur. Ve eğer yapmadıysanız, sizi neyin beklediği hakkında hiçbir fikriniz yok!

Ancak herhangi bir şey için zaman kaybetmeden önce, ondan ne kazanacağınızı sormak iyi bir alışkanlıktır (benim aksime, kim aşırı heyecanlı bir aptaldır ve sırf bunun iyiliği için her şeyi seve seve öğrenir). Bir React geliştiricisi olarak hayatınız, bu işlemenin ne olduğu konusunda endişelenmeden iyi gidiyorsa, neden umursuyorsunuz? Güzel soru, o halde önce buna cevap verelim, sonra render'ın gerçekte ne olduğunu göreceğiz.

React'te oluşturma davranışını anlamak neden önemlidir?

Hepimiz, JSX adı verilen bir şey döndüren bileşenleri (bugünlerde işlevsel) yazarak React'i öğrenmeye başlarız. Ayrıca bu JSX'in bir şekilde sayfada görünen gerçek HTML DOM öğelerine dönüştürüldüğünü anlıyoruz. Durum güncellendikçe sayfalar güncellenir, rotalar beklendiği gibi değişir ve her şey yolundadır. Ancak React'in nasıl çalıştığına dair bu görüş naif ve birçok sorunun kaynağı.

Eksiksiz React tabanlı uygulamalar yazmada genellikle başarılı olsak da, uygulamamızın belirli kısımlarını (veya tüm uygulamayı) oldukça yavaş bulduğumuz zamanlar vardır. Ve en kötü kısmı. . . neden olduğuna dair tek bir ipucumuz yok! Her şeyi doğru yaptık, hiçbir hata veya uyarı görmüyoruz, bileşen tasarımı, kodlama standartları vb. ile ilgili tüm iyi uygulamaları takip ettik ve perde arkasında hiçbir ağ yavaşlığı veya pahalı iş mantığı hesaplaması olmuyor.

Bazen tamamen farklı bir sorun olabilir: Performansta bir sorun yok ama uygulama garip davranıyor. Örneğin, kimlik doğrulama arka ucuna üç API çağrısı yapmak, ancak diğerlerine yalnızca bir çağrı yapmak. Veya bazı sayfalar iki kez yeniden çiziliyor, aynı sayfanın iki oluşturması arasındaki görünür geçiş, sarsıcı bir UX yaratıyor.

Oh hayır! Tekrar olmasın!!

Hepsinden kötüsü, bu gibi durumlarda dışarıdan yardım alınamıyor. Favori geliştirici forumunuza gidip bu soruyu sorarsanız, “Uygulamanıza bakmadan söyleyemem. Buraya minimum çalışan bir örnek ekleyebilir misiniz?” Tabii ki, yasal nedenlerle tüm uygulamayı ekleyemezsiniz, ancak bu parçanın çalışan küçük bir örneği bu sorunu içermeyebilir çünkü tüm sistemle gerçek uygulamada olduğu gibi etkileşime girmez.

Berbat? Evet, bana sorarsan.

Bu yüzden, böylesi ıstıraplı günleri görmek istemiyorsanız, bir anlayış geliştirmenizi öneririm - ve ilgi, ısrar etmeliyim; gönülsüzce kazanılan anlayış, sizi React dünyasında - React'te oluşturma denilen bu az anlaşılan şeyde - fazla uzağa götürmez. İnanın bana, anlamak o kadar da zor değil ve ustalaşması çok zor olsa da, her köşeyi ve huyunu bilmek zorunda kalmadan gerçekten çok ileri gideceksiniz.

React'te render ne anlama geliyor?

Dostum bu harika bir soru. React'i öğrenirken bunu sorma eğiliminde değiliz (biliyorum çünkü yapmadım) çünkü “render” kelimesi belki de bizi yanlış bir aşinalık duygusuna kaptırır. Sözlük anlamı tamamen farklı olsa da (ve bu tartışmada önemli değil), biz programcılar zaten bunun ne anlama geldiğine dair bir fikre sahibiz. Ekranlarla, 3D API'lerle, grafik kartlarıyla çalışmak ve ürün özelliklerini okumak, zihnimizi "render" kelimesini okuduğumuzda "bir resim çiz" satırında bir şeyler düşünmeye yönlendirir. Oyun motoru programlamasında, tek işi tam olarak !, Sahne tarafından verilen dünyayı boyamak olan bir Oluşturucu vardır.

Ve bu yüzden, React'in bir şeyi "işlediği" zaman, tüm bileşenleri topladığını ve web sayfasının DOM'sini yeniden boyadığını düşünüyoruz. Ancak React dünyasında (ve evet, resmi belgelerde bile), render bununla ilgili değildir. O halde, hadi emniyet kemerlerimizi sıkalım ve React'in içindekilere gerçekten derin bir dalış yapalım.

"Lanetleneceğim. . ”

React'in sanal DOM denilen şeyi koruduğunu ve bunu düzenli olarak gerçek DOM ile karşılaştırdığını ve gerektiğinde değişiklikleri uyguladığını duymuş olmalısınız (bu yüzden sadece jQuery ve React'i birlikte kullanamazsınız - React'in tüm kontrolü ele alması gerekir. DOM'yi seçin). Şimdi, bu sanal DOM, gerçek DOM'un yaptığı gibi HTML öğelerinden değil, React öğelerinden oluşuyor. Fark ne? İyi soru! Neden küçük bir React uygulaması oluşturup kendimiz görmüyorsunuz?

Bu çok basit React uygulamasını bu amaçla oluşturdum. Kodun tamamı, yalnızca birkaç satır içeren tek bir dosyadır:

 import React from "react"; import "./styles.css"; export default function App() { const element = ( <div className="App"> <h1>Hello, there!</h1> <h2>Let's take a look inside React elements</h2> </div> ); console.log(element); return element; }

Burada ne yaptığımıza dikkat edin?

Evet, bir JSX öğesinin neye benzediğini günlüğe kaydetmeniz yeterlidir. Bu JSX ifadeleri ve bileşenleri, yüzlerce kez yazdığımız şeylerdir, ancak neler olup bittiğine nadiren dikkat ederiz. Tarayıcınızın geliştirme konsolunu açar ve bu uygulamayı çalıştırırsanız, genişleyen bir Object görürsünüz:

Bu korkutucu görünebilir, ancak birkaç ilginç ayrıntıya dikkat edin:

  • Baktığımız şey, bir DOM düğümü değil, düz, normal bir JavaScript nesnesidir.
  • props özelliğinin bir className of App (kodda ayarlanan CSS sınıfıdır) olduğunu ve bu öğenin iki çocuğu olduğunu söylediğine dikkat edin (bu da eşleşir, alt öğeler <h1> ve <h2> etiketleridir) .
  • _source özelliği, kaynak kodun öğenin gövdesinin nerede başladığını bize söyler. Gördüğünüz gibi, App.js dosyasını kaynak olarak adlandırıyor ve 6 numaralı satırdan bahsediyor. JSX parantezleri React öğesini içerir ; daha sonra bir React.createElement() çağrısına dönüşmeye hizmet ettikleri için bunun bir parçası değiller.
  • __proto__ özelliği bize bu nesnenin tüm özelliklerini türettiğini söyler. JavaScript Object kökünden gelen özellikler, burada baktığımız şeyin yalnızca günlük JavaScript nesneleri olduğu fikrini pekiştiriyor.

Şimdi, sanal DOM denilen şeyin gerçek DOM'a hiç benzemediğini, o sırada UI'yi temsil eden bir React (JavaScript) nesneleri ağacı olduğunu anlıyoruz.

*İÇ ÇEKMEK* . . . Henüz varmadık mı?

Yorgun?

İnan bana, ben de. Bu fikirleri kafamda defalarca çevirmek ve onları mümkün olan en iyi şekilde sunmaya çalışmak ve sonra onları ortaya çıkarmak ve yeniden düzenlemek için kelimeleri düşünmek - kolay değil.

Ama dikkatimiz dağılıyor!

Buraya kadar hayatta kaldıktan sonra, şimdi peşinde olduğumuz soruyu cevaplayabilecek durumdayız: React'te render nedir?

Oluşturma, sanal DOM'de dolaşan ve mevcut durumu, donanımları, yapıyı, kullanıcı arayüzünde istenen değişiklikleri vb. toplayan React motor sürecidir. React şimdi bazı hesaplamaları kullanarak sanal DOM'yi günceller ve ayrıca yeni sonucu gerçek DOM ile karşılaştırır. sayfada. Bu hesaplama ve karşılaştırma, React ekibinin resmi olarak "uzlaşma" dediği şeydir ve onların fikirleri ve ilgili algoritmaları ile ilgileniyorsanız, resmi belgelere göz atabilirsiniz.

Taahhüt Zamanı!

Oluşturma kısmı tamamlandıktan sonra, React, DOM'a gerekli değişiklikleri uyguladığı "commit" adlı bir aşamayı başlatır. Bu değişiklikler eşzamanlı olarak uygulanır (birbiri ardına, ancak aynı anda çalışan yeni bir modun yakında olması beklenir) ve DOM güncellenir. React'in bu değişiklikleri tam olarak ne zaman ve nasıl uyguladığı bizi ilgilendirmiyor, çünkü bu tamamen kaputun altında olan ve React ekibi yeni şeyler denedikçe değişmeye devam etmesi muhtemel bir şey.

React uygulamalarında oluşturma ve performans

Artık oluşturmanın bilgi toplamak anlamına geldiğini ve her seferinde görsel DOM değişikliklerine neden olması gerekmediğini anladık. Ayrıca, "render" olarak düşündüğümüz şeyin, oluşturma ve işlemeyi içeren iki aşamalı bir süreç olduğunu da biliyoruz. Şimdi, React uygulamalarında oluşturmanın (ve daha da önemlisi, yeniden oluşturmanın) nasıl tetiklendiğini ve ayrıntıları bilmemenin uygulamaların nasıl kötü performans göstermesine neden olabileceğini göreceğiz.

Ana bileşendeki değişiklik nedeniyle yeniden oluşturma

React'teki bir ana bileşen değişirse (örneğin durumu veya destekleri değiştiği için), React tüm ağacı bu ana öğeden aşağıya doğru yürütür ve tüm bileşenleri yeniden oluşturur. Uygulamanızda çok sayıda iç içe bileşen ve çok sayıda etkileşim varsa, üst bileşeni her değiştirdiğinizde (değiştirmek istediğinizin yalnızca üst bileşen olduğunu varsayarak) bilmeden büyük bir performans düşüşü yaşıyorsunuz demektir.

Doğru, oluşturma React'in gerçek DOM'yi değiştirmesine neden olmaz çünkü mutabakat sırasında bu bileşenler için hiçbir şeyin değişmediğini algılar. Ancak, yine de CPU zamanı ve bellek israfıdır ve ne kadar çabuk eklendiğine şaşıracaksınız.

Bağlamdaki değişiklik nedeniyle yeniden oluşturma

React'in Bağlam özelliği, herkesin en sevdiği durum yönetimi aracı gibi görünüyor (hiç için oluşturulmamış bir şey). Hepsi çok kullanışlı — sadece en üstteki bileşeni bağlam sağlayıcıya sarın, gerisi basit bir mesele! React uygulamalarının çoğu bu şekilde oluşturuluyor, ancak bu makaleyi buraya kadar okuduysanız, muhtemelen neyin yanlış olduğunu fark etmişsinizdir. Evet, bağlam nesnesi her güncellendiğinde, tüm ağaç bileşenlerinin büyük ölçüde yeniden oluşturulmasını tetikler.

Çoğu uygulamanın performans farkındalığı yoktur, bu nedenle kimse fark etmez, ancak daha önce de belirtildiği gibi, bu tür gözetimler yüksek hacimli, yüksek etkileşimli uygulamalarda çok maliyetli olabilir.

React işleme performansını iyileştirme

Peki, tüm bunlar göz önüne alındığında, uygulamalarımızın performansını iyileştirmek için ne yapabiliriz? Görünüşe göre yapabileceğimiz birkaç şey var, ancak yalnızca işlevsel bileşenler bağlamında tartışacağımızı unutmayın. Sınıf tabanlı bileşenler, React ekibi tarafından son derece caydırılır ve çıkış yolundadır.

Devlet yönetimi için Redux veya benzeri kitaplıkları kullanın

Context'in hızlı ve kirli dünyasını sevenler Redux'tan nefret etme eğilimindedir, ancak bu şey iyi sebeplerden dolayı oldukça popülerdir. Ve bu nedenlerden biri performanstır - Redux'daki connect() işlevi büyülüdür, çünkü (neredeyse her zaman) yalnızca bu bileşenleri gerektiği gibi doğru bir şekilde oluşturur. Evet, standart Redux mimarisini takip edin ve performans bedavaya gelsin. Redux mimarisini benimserseniz, performans (ve diğer) sorunlarının çoğundan hemen kaçınırsınız, bu hiç de abartı değil.

Bileşenleri "dondurmak" için memo() kullanın

"Memo" adı, önbelleğe alma için süslü bir ad olan Memoization'dan gelir. Ve çok fazla önbelleğe alma ile karşılaşmadıysanız, sorun değil; işte sulandırılmış bir açıklama: bir hesaplama/işlem sonucuna her ihtiyaç duyduğunuzda, önceki sonuçları koruduğunuz yere bakarsınız; bulursanız, harika, sadece bu sonucu döndürün; değilse, devam edin ve bu işlemi/hesabı yapın.

Doğrudan memo() 'ya dalmadan önce, React'te gereksiz oluşturmanın nasıl gerçekleştiğini görelim. Basit bir senaryo ile başlıyoruz: Kullanıcıya hizmeti/ürünü kaç kez beğendiğini gösteren uygulama kullanıcı arayüzünün küçük bir parçası (kullanım durumunu kabul etmekte sorun yaşıyorsanız, Medium'da nasıl "alkışlayabileceğinizi" düşünün. ” bir makaleyi ne kadar desteklediğinizi/beğendiğinizi göstermek için birden çok kez).

Ayrıca beğenileri 1 artırmalarını sağlayan bir düğme de var. Son olarak, içinde kullanıcılara temel hesap ayrıntılarını gösteren başka bir bileşen var. Bunu takip etmekte zorlanıyorsanız hiç endişelenmeyin; Şimdi her şey için adım adım kod vereceğim (ve çok fazla değil) ve sonunda, çalışan uygulamayla uğraşıp anlayışınızı geliştirebileceğiniz bir oyun alanına bir bağlantı vereceğim.

Önce müşteri bilgileriyle ilgili bileşeni ele alalım. Aşağıdaki kodu içeren CustomerInfo.js adında bir dosya oluşturalım:

 import React from "react"; export const CustomerInfo = () => { console.log("CustomerInfo was rendered! :O"); return ( <React.Fragment> <p>Name: Sam Punia</p> <p>Email: [email protected]</p> <p>Preferred method: Online</p> </React.Fragment> ); };

Süslü bir şey yok, değil mi?

Kullanıcı uygulamayla etkileşime girdikçe değişmesi beklenmeyen (sahnelerden geçirilmiş olabilecek) yalnızca bazı bilgilendirici metinler (dışarıdaki saflar için evet, değişebileceğinden emin olabilirsiniz, ancak mesele şu ki, geri kalanıyla karşılaştırıldığında Uygulamanın pratikte statiktir). Ancak console.log() deyimine dikkat edin. Bu, bileşenin oluşturulduğunu bilmek için ipucumuz olacaktır ("render" ifadesinin, bilgilerinin toplandığı ve hesaplandığı/karşılaştırıldığı anlamına geldiğini ve gerçek DOM'ye boyanmadığı anlamına geldiğini unutmayın).

Dolayısıyla, testimiz sırasında tarayıcı konsolunda böyle bir mesaj görmezsek, bileşenimiz hiç işlenmemiştir; 10 kez göründüğünü görürsek, bileşenin 10 kez oluşturulduğu anlamına gelir; ve benzeri.

Şimdi ana bileşenimizin bu müşteri bilgisi bileşenini nasıl kullandığını görelim:

 import React, { useState } from "react"; import "./styles.css"; import { CustomerInfo } from "./CustomerInfo"; export default function App() { const [totalLikes, setTotalLikes] = useState(0); return ( <div className="App"> <div className="LikesCounter"> <p>You have liked us {totalLikes} times so far.</p> <button onClick={() => setTotalLikes(totalLikes + 1)}> Click here to like again! </button> </div> <div className="CustomerInfo"> <CustomerInfo /> </div> </div> ); }

Böylece, App bileşeninin useState() kancası aracılığıyla dahili bir durum tarafından yönetilen bir iç duruma sahip olduğunu görüyoruz. Bu durum, kullanıcının hizmeti/siteyi kaç kez beğendiğini saymaya devam eder ve başlangıçta sıfıra ayarlanır. React uygulamaları söz konusu olduğunda zorlayıcı bir şey yok, değil mi? UI tarafında, işler şöyle görünür:

Düğme ezilemeyecek kadar çekici görünüyor, en azından benim için! Ama bunu yapmadan önce tarayıcımın geliştirme konsolunu açıp temizleyeceğim. Ondan sonra, düğmeye birkaç kez basacağım ve işte şunu görüyorum:

Düğmeye 19 kez bastım ve beklendiği gibi toplam beğeni sayısı 19'da kaldı. Renk şeması okumayı gerçekten zorlaştırıyordu, bu yüzden ana şeyi vurgulamak için kırmızı bir kutu ekledim: <CustomerInfo /> bileşeni 20 kez işlendi!

Neden 20?

Bir kez, her şey başlangıçta oluşturulduğunda ve ardından, düğmeye basıldığında 19 kez. Düğme, <App /> bileşeni içindeki bir durum parçası olan totalLikes değiştirir ve bunun sonucunda ana bileşen yeniden oluşturulur. Ve bu yazının önceki bölümlerinde öğrendiğimiz gibi, içindeki tüm bileşenler de yeniden işleniyor. Bu istenmeyen bir durumdur, çünkü <CustomerInfo /> bileşeni süreçte değişmedi ve yine de oluşturma sürecine katkıda bulundu.

Bunu nasıl önleyebiliriz?

Tam olarak bu bölümün başlığında belirtildiği gibi, <CustomerInfo /> bileşeninin "korunmuş" veya önbelleğe alınmış bir kopyasını oluşturmak için memo() işlevini kullanmak. React, memoized bir bileşenle, sahne öğelerine bakar ve bunları önceki aksesuarlarla karşılaştırır ve herhangi bir değişiklik olmazsa, React bu bileşenden yeni bir “render” çıktısı çıkarmaz.

Bu kod satırını CustomerInfo.js dosyamıza ekleyelim:

 export const MemoizedCustomerInfo = React.memo(CustomerInfo);

Evet, tek yapmamız gereken bu! Şimdi bunu ana bileşenimizde kullanmanın ve bir şeylerin değişip değişmediğini görmenin zamanı geldi:

 import React, { useState } from "react"; import "./styles.css"; import { MemoizedCustomerInfo } from "./CustomerInfo"; export default function App() { const [totalLikes, setTotalLikes] = useState(0); return ( <div className="App"> <div className="LikesCounter"> <p>You have liked us {totalLikes} times so far.</p> <button onClick={() => setTotalLikes(totalLikes + 1)}> Click here to like again! </button> </div> <div className="CustomerInfo"> <MemoizedCustomerInfo /> </div> </div> ); }

Evet, sadece iki satır değişti ama yine de tüm bileşeni göstermek istedim. Kullanıcı arayüzü açısından hiçbir şey değişmedi, bu nedenle yeni sürümü bir tur atıp beğen düğmesini birkaç kez kırarsam şunu elde ederim:

Peki, kaç tane konsol mesajımız var?

Sadece bir! Bu, ilk oluşturma dışında bileşene hiç dokunulmadığı anlamına gelir. Gerçekten yüksek ölçekli bir uygulamada performans kazanımlarını hayal edin! Tamam, tamam, söz verdiğim kod oyun alanının bağlantısı burada. Önceki örneği çoğaltmak için CustomerInfo.js MemoizedCustomerInfo yerine CustomerInfo içe aktarmanız ve kullanmanız gerekir.

Bununla birlikte, memo() her yere serpebileceğiniz ve sihirli sonuçlar bekleyebileceğiniz sihirli bir kum değildir. memo() işlevinin aşırı kullanımı da uygulamanızda zor hatalara neden olabilir ve bazen de bazı beklenen güncellemelerin başarısız olmasına neden olabilir. “Erken” optimizasyonla ilgili genel tavsiye burada da geçerlidir. İlk olarak, sezginizin dediği gibi uygulamanızı oluşturun; daha sonra, hangi parçaların yavaş olduğunu görmek için yoğun bir profil oluşturma yapın ve not alınan bileşenlerin doğru çözüm olduğu görülüyorsa, ancak o zaman bunu tanıtın.

"Akıllı" bileşen tasarımı

"Akıllı" kelimesini tırnak içine aldım çünkü: 1) Zeka oldukça öznel ve durumsaldır; 2) Sözde akıllı eylemlerin genellikle hoş olmayan sonuçları olur. Bu nedenle, bu bölüm için tavsiyem şudur: Yaptığınız şeye çok fazla güvenmeyin.

Bunun dışında, işleme performansını iyileştirmenin bir yolu, bileşenleri biraz farklı şekilde tasarlamak ve yerleştirmektir. Örneğin, bir alt bileşen yeniden oluşturulabilir ve yeniden oluşturmalardan kaçınmak için hiyerarşide bir yere taşınabilir. Hiçbir kural "ChatPhotoView bileşeni her zaman Chat bileşeninin içinde olmalıdır" demez. Özel durumlarda (ve bunlar, performansın etkilendiğine dair veriye dayalı kanıtlarımızın olduğu durumlardır), kuralları esnetmek/kırmak aslında harika bir fikir olabilir.

Çözüm

Genel olarak React uygulamalarını optimize etmek için çok daha fazlası yapılabilir, ancak bu makale Rendering üzerine olduğu için tartışmanın kapsamını kısıtladım. Ne olursa olsun, artık React'te neler olup bittiğine, render'ın gerçekte ne olduğuna ve uygulama performansını nasıl etkileyebileceğine dair daha iyi bir anlayışa sahip olduğunuzu umuyorum.

Ardından, React Hooks'un ne olduğunu anlayalım.