Componente eficiente React: un ghid pentru optimizarea performanței React

Publicat: 2022-03-11

De la introducerea sa, React a schimbat modul în care dezvoltatorii front-end gândesc să construiască aplicații web. Cu DOM virtual, React face ca actualizările UI să fie cât mai eficiente, făcând aplicația dvs. web mai rapidă. Dar, de ce aplicațiile web React de dimensiuni moderate tind încă să aibă performanțe slabe?

Ei bine, indiciul este în modul în care utilizați React.

O bibliotecă front-end modernă, cum ar fi React, nu vă face aplicația mai rapidă în mod magic. Este necesar ca dezvoltatorul să înțeleagă cum funcționează React și cum trăiesc componentele prin diferitele faze ale ciclului de viață al componentei.

Cu React, puteți obține o mulțime de îmbunătățiri ale performanței pe care le are de oferit, măsurând și optimizând modul și când randarea componentelor dvs. Și, React oferă doar instrumentele și funcționalitățile necesare pentru a face acest lucru ușor.

Accelerează aplicația React prin optimizarea procesului de randare a componentelor.

În acest tutorial React, veți afla cum puteți măsura performanța componentelor dvs. React și le puteți optimiza pentru a construi o aplicație web React mult mai performantă. Veți afla, de asemenea, cum câteva dintre cele mai bune practici JavaScript vă ajută să faceți ca aplicația dvs. web React să ofere o experiență de utilizator mult mai fluentă.

Cum funcționează React?

Înainte de a ne aprofunda în tehnicile de optimizare, trebuie să înțelegem mai bine cum funcționează React.

În centrul dezvoltării React, aveți sintaxa JSX simplă și evidentă și capacitatea React de a construi și compara DOM-uri virtuale. De la lansare, React a influențat multe alte biblioteci front-end. Bibliotecile precum Vue.js se bazează, de asemenea, pe ideea de DOM-uri virtuale.

Iată cum funcționează React:

Fiecare aplicare React începe cu o componentă rădăcină și este compusă din mai multe componente dintr-o formațiune de arbore. Componentele din React sunt „funcții” care redau interfața de utilizare pe baza datelor (recuzite și stare) pe care le primește.

Putem simboliza acest lucru ca F .

 UI = F(data)

Utilizatorii interacționează cu interfața de utilizare și provoacă modificarea datelor. Indiferent dacă interacțiunea implică apasarea unui buton, atingerea unei imagini, tragerea elementelor din listă, solicitările AJAX care invocă API-uri etc., toate aceste interacțiuni modifică doar datele. Nu provoacă niciodată schimbarea directă a interfeței de utilizare.

Aici, datele sunt tot ceea ce definește starea aplicației web și nu doar ceea ce ați stocat în baza de date. Chiar și biți de stări front-end (de exemplu, ce filă este selectată în prezent sau dacă o casetă de selectare este bifată în prezent) fac parte din aceste date.

Ori de câte ori există o modificare a acestor date, React folosește funcțiile componente pentru a reda interfața de utilizare, dar numai virtual:

 UI1 = F(data1) UI2 = F(data2)

React calculează diferențele dintre interfața de utilizare actuală și noua interfață de utilizare prin aplicarea unui algoritm de comparație pe cele două versiuni ale DOM-ului său virtual.

 Changes = Diff(UI1, UI2)

React apoi continuă să aplice numai modificările UI la interfața de utilizare reală din browser.

Când datele asociate cu o componentă se modifică, React determină dacă este necesară o actualizare reală a DOM. Acest lucru permite React să evite operațiunile de manipulare DOM potențial costisitoare în browser, cum ar fi crearea de noduri DOM și accesarea celor existente dincolo de necesitate.

Această diferențiere și redare repetată a componentelor poate fi una dintre sursele principale ale problemelor de performanță React în orice aplicație React. Construirea unei aplicații React în care algoritmul de diferență nu reușește să se reconcilieze eficient, ceea ce face ca întreaga aplicație să fie redată în mod repetat poate duce la o experiență frustrant de lentă.

De unde să începem optimizarea?

Dar ce anume optimizăm?

Vedeți, în timpul procesului inițial de randare, React construiește un arbore DOM astfel:

Un DOM virtual de componente React

Având în vedere o parte din modificările datelor, ceea ce dorim să facă React este să redă din nou doar componentele care sunt direct afectate de modificare (și, eventual, să omitem chiar și procesul de diferență pentru restul componentelor):

Reacționează redând un număr optim de componente

Cu toate acestea, ceea ce face React este:

Reacționați irosind resurse, redând toate componentele

În imaginea de mai sus, toate nodurile galbene sunt redate și diferențiate, rezultând timp pierdut/resurse de calcul. Aici ne vom depune în primul rând eforturile de optimizare. Configurarea fiecărei componente pentru render-diff numai atunci când este necesar ne va permite să recuperăm aceste cicluri CPU irosite.

Dezvoltatorii bibliotecii React au luat în considerare acest lucru și ne-au oferit un cârlig pentru a face exact asta: o funcție care ne permite să spunem lui React când este în regulă să omitem randarea unei componente.

Măsurând mai întâi

După cum spune Rob Pike destul de elegant, ca una dintre regulile sale de programare:

Măsura. Nu acordați viteză până când nu ați măsurat și chiar și atunci nu o faceți decât dacă o parte a codului copleșește restul.

Nu începeți să optimizați codul care credeți că vă poate încetini aplicația. În schimb, lăsați instrumentele de măsurare a performanței React să vă ghideze prin drum.

React are un instrument puternic doar pentru asta. Folosind biblioteca react-addons-perf , puteți obține o imagine de ansamblu asupra performanței generale a aplicației dvs.

Utilizarea este foarte simpla:

 Import Perf from 'react-addons-perf' Perf.start(); // use the app Perf.stop(); Perf.printWasted();

Aceasta va imprima un tabel cu cantitatea de timp pierdută pentru componente la randare.

Tabelul componentelor care pierd timpul la randare

Biblioteca oferă alte funcții care vă permit să imprimați diferite aspecte ale timpului pierdut separat (de exemplu, folosind funcțiile printInclusive() sau printExclusive() ), sau chiar să imprimați operațiunile de manipulare DOM (folosind funcția printOperations() ).

Făcând benchmarking-ul cu un pas mai departe

Dacă ești o persoană vizuală, atunci react-perf-tool este exact lucrul de care ai nevoie.

react-perf-tool se bazează pe biblioteca react-addons-perf . Vă oferă o modalitate mai vizuală de a depana performanța aplicației dvs. React. Folosește biblioteca de bază pentru a obține măsurători și apoi le vizualizează sub formă de grafice.

O vizualizare a componentelor care pierd timp în randare

De cele mai multe ori, aceasta este o modalitate mult mai convenabilă de a identifica blocajele. Îl poți folosi cu ușurință adăugându-l ca componentă la aplicația ta.

Ar trebui React să actualizeze componenta?

În mod implicit, React va rula, va reda DOM-ul virtual și va compara diferența pentru fiecare componentă din arbore pentru orice modificare a elementelor de recuzită sau a stării sale. Dar acest lucru, evident, nu este rezonabil.

Pe măsură ce aplicația dvs. crește, încercarea de a reda și de a compara întregul DOM virtual la fiecare acțiune va încetini în cele din urmă.

React oferă dezvoltatorului o modalitate simplă de a indica dacă o componentă are nevoie de redare. Aici intervine metoda shouldComponentUpdate .

 function shouldComponentUpdate(nextProps, nextState) { return true; }

Când această funcție returnează true pentru orice componentă, permite declanșarea procesului de randare-diff.

Acest lucru vă oferă o modalitate simplă de a controla procesul de render-diff. Ori de câte ori trebuie să preveniți ca o componentă să fie redată din nou, pur și simplu returnați false din funcție. În interiorul funcției, puteți compara setul curent și următorul de elemente de recuzită și stare pentru a determina dacă este necesară o redare:

 function shouldComponentUpdate(nextProps, nextState) { return nextProps.id !== this.props.id; }

Folosind un React.PureComponent

Pentru a ușura și automatiza puțin această tehnică de optimizare, React oferă ceea ce este cunoscut sub numele de componentă „pură”. Un React.PureComponent este exact ca un React.Component care implementează o shouldComponentUpdate() cu un suport superficial și o comparație de stare.

Un React.PureComponent este mai mult sau mai puțin echivalent cu acesta:

 class MyComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { return shallowCompare(this.props, nextProps) && shallowCompare(this.state, nextState); } … }

Deoarece efectuează doar o comparație superficială, s-ar putea să-l găsiți util numai atunci când:

  • Elementele de recuzită sau stările tale conțin date primitive.
  • Elementele de recuzită și stările dvs. au date complexe, dar știți când să apelați forceUpdate() pentru a vă actualiza componenta.

Facerea datelor imuabile

Ce se întâmplă dacă ai putea folosi un React.PureComponent , dar ai avea totuși un mod eficient de a spune când orice elemente de recuzită sau stări complexe s-au schimbat automat? Aici structurile de date imuabile fac viața mai ușoară.

Ideea din spatele utilizării structurilor de date imuabile este simplă. Ori de câte ori un obiect care conține date complexe se modifică, în loc să faceți modificări în acel obiect, creați o copie a acelui obiect cu modificările. Acest lucru face ca detectarea modificărilor datelor să fie la fel de simplă precum compararea referințelor celor două obiecte.

Puteți folosi Object.assign sau _.extend (din Underscore.js sau Lodash):

 const newValue2 = Object.assign({}, oldValue); const newValue2 = _.extend({}, oldValue);

Și mai bine, puteți folosi o bibliotecă care oferă structuri de date imuabile:

 var map1 = Immutable.Map({a:1, b:2, c:3}); var map2 = map1.set('b', 2); assert(map1.equals(map2) === true); var map3 = map1.set('b', 50); assert(map1.equals(map3) === false);

Aici, Immutable.Map este furnizat de biblioteca Immutable.js.

De fiecare dată când o hartă este actualizată cu metoda sa set , o nouă hartă este returnată numai dacă operația setată a modificat valoarea de bază. În caz contrar, se returnează aceeași hartă.

Puteți afla mai multe despre utilizarea structurilor de date imuabile aici.

Mai multe tehnici de optimizare a aplicației React

Folosind versiunea de producție

Când dezvoltați o aplicație React, vi se prezintă avertismente și mesaje de eroare cu adevărat utile. Acestea fac ca identificarea erorilor și a problemelor în timpul dezvoltării să fie o fericire. Dar au un cost de performanță.

Dacă te uiți în codul sursă al lui React, vei vedea o mulțime de verificări if (process.env.NODE_ENV != 'production') . Aceste bucăți de cod pe care React le rulează în mediul dumneavoastră de dezvoltare nu sunt necesare utilizatorului final. Pentru mediile de producție, tot acest cod inutil poate fi eliminat.

Dacă v-ați pornit proiectul folosind create-react-app , atunci puteți pur și simplu să rulați npm run build pentru a produce versiunea de producție fără acest cod suplimentar. Dacă utilizați Webpack direct, puteți rula webpack -p (care este echivalent cu webpack --optimize-minimize --define process.env.NODE_ENV="'production'" .

Funcții de legare devreme

Este foarte obișnuit să vedeți funcții legate de contextul componentei în interiorul funcției de randare. Acesta este adesea cazul când folosim aceste funcții pentru a gestiona evenimente ale componentelor copil.

 // Creates a new `handleUpload` function during each render() <TopBar onUpload={this.handleUpload.bind(this)} /> // ...as do inlined arrow functions <TopBar onUpload={files => this.handleUpload(files)} />

Acest lucru va face ca funcția render() să creeze o nouă funcție la fiecare randare. O modalitate mult mai bună de a face același lucru este:

 class App extends React.Component { constructor(props) { super(props); this.handleUpload = this.handleUpload.bind(this); } render() { … <TopBar onUpload={this.handleUpload} /> … } }

Utilizarea mai multor fișiere cu bucăți

Pentru aplicațiile web React cu o singură pagină, deseori ajungem să grupăm tot codul JavaScript front-end într-un singur fișier comprimat. Acest lucru funcționează bine pentru aplicațiile web de dimensiuni mici până la moderate. Dar, pe măsură ce aplicația începe să crească, livrarea acestui fișier JavaScript în pachet în browser poate deveni un proces consumator de timp.

Dacă utilizați Webpack pentru a vă construi aplicația React, puteți profita de capacitățile sale de împărțire a codului pentru a separa codul aplicației construite în mai multe „bucăți” și a le livra browserului după cum este necesar.

Există două tipuri de împărțire: împărțirea resurselor și împărțirea codului la cerere.

Cu împărțirea resurselor, împărțiți conținutul resurselor în mai multe fișiere. De exemplu, folosind CommonsChunkPlugin, puteți extrage cod comun (cum ar fi toate bibliotecile externe) într-un fișier „bucătă” propriu. Folosind ExtractTextWebpackPlugin, puteți extrage tot codul CSS într-un fișier CSS separat.

Acest tip de împărțire va ajuta în două moduri. Ajută browserul să memoreze în cache acele resurse care se schimbă mai rar. De asemenea, va ajuta browserul să profite de descărcarea paralelă pentru a reduce potențial timpul de încărcare.

O caracteristică mai notabilă a Webpack este împărțirea codului la cerere. Îl puteți folosi pentru a împărți codul într-o bucată care poate fi încărcată la cerere. Acest lucru poate menține descărcarea inițială mică, reducând timpul necesar pentru încărcarea aplicației. Browserul poate descărca apoi alte bucăți de cod la cerere, atunci când este nevoie de aplicație.

Puteți afla mai multe despre împărțirea codului Webpack aici.

Activarea Gzip pe serverul dvs. web

Fișierele JS ale pachetului aplicației React sunt de obicei foarte mari, așa că pentru a face pagina web să se încarce mai rapid, putem activa Gzip pe serverul web (Apache, Nginx etc.)

Toate browserele moderne acceptă și negociază automat compresia Gzip pentru solicitările HTTP. Activarea compresiei Gzip poate reduce dimensiunea răspunsului transferat cu până la 90%, ceea ce poate reduce semnificativ timpul de descărcare a resursei, poate reduce utilizarea datelor pentru client și poate îmbunătăți timpul pentru prima redare a paginilor dvs.

Verificați documentația pentru serverul dvs. web despre cum să activați compresia:

  • Apache: Folosiți mod_deflate
  • Nginx: Utilizați ngx_http_gzip_module

Folosind Eslint-plugin-react

Ar trebui să utilizați ESLint pentru aproape orice proiect JavaScript. React nu este diferit.

Cu eslint-plugin-react , vă veți forța să vă adaptați la o mulțime de reguli în programarea React care vă pot aduce beneficii codului pe termen lung și pot evita multe probleme și probleme comune care apar din cauza codului scris prost.

Faceți din nou aplicațiile web React rapid

Pentru a profita la maximum de React, trebuie să folosiți instrumentele și tehnicile sale. Performanța unei aplicații web React constă în simplitatea componentelor sale. Covârșirea algoritmului de redare diferentă poate face ca aplicația dvs. să funcționeze slab în moduri frustrante.

Înainte de a vă putea optimiza aplicația, va trebui să înțelegeți cum funcționează componentele React și cum sunt redate în browser. Metodele ciclului de viață React vă oferă modalități de a preveni redarea inutilă a componentei dvs. Eliminați aceste blocaje și veți avea performanța aplicației pe care utilizatorii dvs. o merită.

Deși există mai multe modalități de optimizare a unei aplicații web React, reglarea fină a componentelor pentru a se actualiza numai atunci când este necesar oferă cea mai bună îmbunătățire a performanței.

Cum măsurați și optimizați performanța aplicației dvs. web React? Distribuie-l în comentariile de mai jos.

Înrudit : Preluarea datelor învechite în timp ce se revalidează cu React Hooks: un ghid