Sådan identificeres og løses spildte gengivelser i React

Så for nylig tænkte jeg på præstationsprofilering af en reageringsapp, som jeg arbejdede på, og tænkte pludselig at indstille et par præstationsmålinger. Og jeg stødte på, at det første, jeg har brug for at tackle, er spildte gengivelser, jeg laver på hver af websiderne. Du tænker måske over, hvad der er spildte gengivelser forresten? Lad os dykke ned.

Fra starten har React ændret hele filosofien om at opbygge webapps og efterfølgende den måde, front-end-udviklere tænker på. Med sin introduktion af Virtual DOM gør React UI-opdateringer så effektive som de nogensinde kan være. Dette gør webapp-oplevelsen pæn. Har du nogensinde spekuleret på, hvordan du gør dine React-applikationer hurtigere? Hvorfor har React-webapps i moderat størrelse stadig en tendens til at fungere dårligt? Problemerne ligger i, hvordan vi rent faktisk bruger React!

Sådan fungerer React

Et moderne frontend-bibliotek som React gør ikke vores app hurtigere vidunderligt. For det første skal vi udviklere forstå, hvordan React fungerer. Hvordan lever komponenterne gennem komponentens livscyklus i applikationernes levetid? Så før vi dykker ned i enhver optimeringsteknik, skal vi have en bedre forståelse af, hvordan React rent faktisk fungerer under emhætten.

Kernen i React har vi JSX-syntaksen og React's stærke evne til at opbygge og sammenligne virtuelle DOM'er. Siden udgivelsen har React påvirket mange andre front-end-biblioteker. For eksempel er Vue.js også afhængig af ideen om virtuelle DOM'er.

Hver React-applikation begynder med en rodkomponent. Vi kan tænke på hele applikationen som en trædannelse, hvor hver knude er en komponent. Komponenter i React er 'funktioner', der gengiver brugergrænsefladen baseret på dataene. Det betyder rekvisitter og angive, at det modtager; sig det erCF

UI = CF(data)

Brugere interagerer med brugergrænsefladen og forårsager ændring i data. Interaktionerne er alt, hvad en bruger kan gøre i vores applikation. For eksempel ved at klikke på en knap, glide billeder, trække listeelementer rundt og AJAX anmoder om at påkalde API'er. Alle disse interaktioner ændrer kun dataene. De forårsager aldrig nogen ændring i brugergrænsefladen.

Her er data alt, hvad der definerer tilstanden for en applikation. Ikke kun hvad vi har gemt i vores database. Selv forskellige front-end-stater som hvilken fane der aktuelt er valgt, eller om et afkrydsningsfelt i øjeblikket er markeret eller ej, er en del af disse data. Hver gang der sker en ændring i data, bruger React komponentfunktionerne til at gengive brugergrænsefladen igen, men kun virtuelt:

UI1 = CF(data1)UI2 = CF(data2)

React beregner forskellene mellem den aktuelle brugergrænseflade og den nye brugergrænseflade ved at anvende en sammenligningsalgoritme på de to versioner af dens virtuelle DOM.

Changes = Difference(UI1, UI2)

React fortsætter derefter med kun at anvende UI-ændringerne på den virkelige UI i browseren. Når dataene, der er knyttet til en komponent, ændres, bestemmer React, om der kræves en egentlig DOM-opdatering. Dette gør det muligt for React at undgå potentielt dyre DOM-manipulationshandlinger i browseren. Eksempler som oprettelse af DOM-noder og adgang til eksisterende ud over nødvendighed.

Denne gentagne differentiering og gengivelse af komponenter kan være en af ​​de primære kilder til React-ydelsesproblemer i enhver React-app. Opbygning af en React-app, hvor den differentierende algoritme ikke kan forene effektivt, hvilket får hele appen til at blive gengivet gentagne gange, hvilket faktisk forårsager spildte gengivelser, og det kan resultere i en frustrerende langsom oplevelse.

Under den indledende gengivelsesproces bygger React et DOM-træ som dette -

Antag at en del af dataene ændres. Hvad vi ønsker, at React skal gøre, er kun at gengive de komponenter, der er direkte påvirket af den specifikke ændring. Spring muligvis over selv differentieringsprocessen for resten af ​​komponenterne. Lad os sige nogle dataændringer i komponent 2i ovenstående billede, og disse data er videregivet Rtil Bog derefter 2. Hvis R gengiver igen, gengiver det hver af sine børn igen, hvilket betyder A, B, C, D, og ​​ved denne proces, hvad der faktisk reagerer er dette:

På billedet ovenfor gengives og differentieres alle de gule knudepunkter. Dette resulterer i spildt tid / beregningsressourcer. Det er her, vi primært vil optimere vores indsats. Konfiguration af hver komponent til kun at gengive og differentiere, når det er nødvendigt. Dette giver os mulighed for at genvinde de spildte CPU-cyklusser. Først skal vi se på den måde, hvorpå vi kan identificere spildte gengivelser af vores ansøgning.

Identificer spildte gengivelser

Der er et par forskellige måder at gøre dette på. Den enkleste metode er at skifte til valgmuligheden for opdateringer til fremhævning i præferencen React dev-værktøjer.

Under interaktion med appen fremhæves opdateringer på skærmen med farvede kanter. Ved denne proces skal du se komponenter, der er gengivet igen. Dette giver os mulighed for at få øje på gengivelser, der ikke var nødvendige.

Lad os følge dette eksempel.

Bemærk, at når vi indtaster en anden todo, blinker den første 'todo' også på skærmen ved hvert tastetryk. Dette betyder, at det gengives igen af ​​React sammen med input. Dette er hvad vi kalder en "spildt" gengivelse. Vi ved, at det er unødvendigt, fordi det første todo-indhold ikke er ændret, men React ved det ikke.

Selvom React kun opdaterer de ændrede DOM-noder, tager gengivelse stadig noget tid. I mange tilfælde er det ikke et problem, men hvis afmatningen er synlig, bør vi overveje et par ting for at stoppe disse overflødige gengivelser.

Brug af shouldComponentUpdate-metoden

Som standard gengiver React den virtuelle DOM og sammenligner forskellen for hver komponent i træet for enhver ændring i dens rekvisitter eller tilstand. Men det er naturligvis ikke rimeligt. Når vores app vokser, forsøger det til sidst at forsøge at gengive og sammenligne hele den virtuelle DOM ved hver handling.

React giver en enkel livscyklusmetode til at indikere, om en komponent har brug for gengivelse, og det vil sige, shouldComponentUpdatesom udløses, før gendannelsesprocessen starter. Standardimplementeringen af ​​denne funktion vender tilbage true.

Når denne funktion returnerer sand for en hvilken som helst komponent, tillader den, at gengivelsesdifferentieringsprocessen udløses. Dette giver os styrken til at gøre gengivelsesdifferentieringsprocessen. Antag, at vi har brug for at forhindre en komponent i at blive gengivet igen, vi skal simpelthen vende tilbage falsefra den funktion. Som vi kan se fra implementeringen af ​​metoden, kan vi sammenligne de aktuelle og næste rekvisitter og tilstand for at afgøre, om en gengivelse er nødvendig:

Brug af rene komponenter

Når du arbejder på React, ved du bestemt, React.Componentmen hvad er det med React.PureComponent? Vi har allerede diskuteret shouldComponentUpdate livscyklusmetoden, i rene komponenter er der allerede en standardimplementering af shouldComponentUpdate()med en lav sammenligning af prop og tilstand. Så en ren komponent er en komponent, der kun gengiver igen, hvis den props/stateer forskellig fra de tidligere rekvisitter og tilstand .

I lav sammenligning sammenlignes primitive datatyper som streng, boolsk, antal efter værdi, og komplekse datatyper som matrix, objekt, funktion sammenlignes med reference

Men hvad nu hvis vi har en funktionel statsløs komponent, hvor vi skal implementere denne sammenligningsmetode, inden hver gengivelse sker? React har en komponent med højere ordre React.memo. Det er som React.PureComponentfor funktionelle komponenter i stedet for klasser.

Som standard gør det det samme som shouldComponentUpdate (), som kun overfladisk sammenligner rekvisita-objektet. Men hvis vi vil have kontrol over denne sammenligning? Vi kan også tilbyde en tilpasset sammenligningsfunktion som det andet argument.

Gør data uforanderlige

Hvad hvis vi kunne bruge en, React.PureComponentmen stadig har en effektiv måde at fortælle, når komplekse rekvisitter eller tilstande som et array, objekt osv. Har ændret sig automatisk? Det er her, den uforanderlige datastruktur gør livet lettere.

Ideen bag brugen af ​​uforanderlige datastrukturer er enkel. Som vi har diskuteret tidligere, udføres sammenligningen for komplekse datatyper i forhold til deres reference. Hver gang et objekt, der indeholder komplekse data, ændres, i stedet for at foretage ændringer i objektet, kan vi oprette en kopi af det objekt med de ændringer, der skaber en ny reference.

ES6 har objektspredningsoperatør for at få dette til at ske.

Vi kan også gøre det samme for arrays:

Undgå at sende en ny reference til de samme gamle data

Vi ved, at når propsen komponent ændres, sker der en gengivelse. Men nogle gange propsændrede det sig ikke. Vi skriver kode på en måde, som React mener, at den ændrede sig, og det vil også medføre en gengivelse, men denne gang er den en spildt gengivelse. Så grundlæggende skal vi sørge for, at vi sender en anden reference som rekvisitter til forskellige data. Vi er også nødt til at undgå at sende en ny reference til de samme data. Nu ser vi på nogle tilfælde, hvor vi opretter dette problem. Lad os se på denne kode.

Her er indholdet af BookInfokomponenten, hvor vi gengiver to komponenter, BookDescriptionog BookReview. Dette er den korrekte kode, og den fungerer fint, men der er et problem. BookDescriptiongengives igen, når vi får nye anmeldelsesdata som rekvisitter. Hvorfor? Så snart BookInfokomponenten modtager nye rekvisitter, renderkaldes funktionen til at oprette sit elementtræ. Gengivelsesfunktionen opretter en ny bookkonstant, der betyder, at der oprettes en ny reference. Så BookDescriptionvil få dette booksom en nyhedsreference, der vil medføre gengivelse af BookDescription. Så vi kan omlægge dette stykke kode til dette:

Nu er referencen altid den samme, this.bookog et nyt objekt oprettes ikke på gengivelsestidspunktet. Denne gengivelsesfilosofi gælder for alle propinklusive begivenhedshåndterere, som:

Her har vi brugt to forskellige måder (bindingsmetoder og brug af pilfunktion i gengivelse) til at påkalde begivenhedshåndteringsmetoderne, men begge opretter en ny funktion hver gang komponenten gengives igen. Så for at løse disse problemer kan vi binde metoden i constructorog bruge klasseegenskaber, der stadig er i eksperimentel og ikke standardiseret endnu, men så mange devs bruger allerede denne metode til at overføre funktioner til andre komponenter i produktionsklare applikationer:

Afslutter

Internt bruger React flere smarte teknikker til at minimere antallet af dyre DOM-operationer, der kræves for at opdatere brugergrænsefladen. I mange applikationer vil brug af React føre til en hurtig brugergrænseflade uden at gøre meget arbejde for at optimere specifikt til ydeevne. Ikke desto mindre, hvis vi kan følge de teknikker, jeg har nævnt ovenfor for at løse spildte gengivelser, får vi for store applikationer også en meget glat oplevelse med hensyn til ydeevne.