En omfattende guide for å optimalisere React-applikasjoner ved å forhindre unødvendige re-renders. Lær teknikker som memoization, PureComponent, shouldComponentUpdate og mer for bedre ytelse.
Optimalisering av React Rendering: Mestring av å forhindre unødvendige re-renders
React, et kraftig JavaScript-bibliotek for å bygge brukergrensesnitt, kan noen ganger lide av ytelsesflaskehalser på grunn av for mange eller unødvendige re-renders. I komplekse applikasjoner med mange komponenter kan disse re-renders betydelig redusere ytelsen, noe som fører til en treg brukeropplevelse. Denne guiden gir en omfattende oversikt over teknikker for å forhindre unødvendige re-renders i React, og sikrer at applikasjonene dine er raske, effektive og responsive for brukere over hele verden.
Forstå re-renders i React
Før vi dykker ned i optimaliseringsteknikker, er det avgjørende å forstå hvordan Reacts renderingsprosess fungerer. Når en komponents state eller props endres, utløser React en re-render av den komponenten og dens barn. Denne prosessen innebærer å oppdatere den virtuelle DOM-en og sammenligne den med den forrige versjonen for å bestemme det minimale settet med endringer som skal brukes på den faktiske DOM-en.
Imidlertid krever ikke alle endringer i state eller props en DOM-oppdatering. Hvis den nye virtuelle DOM-en er identisk med den forrige, er re-renderen i hovedsak bortkastede ressurser. Disse unødvendige re-renders bruker verdifulle CPU-sykluser og kan føre til ytelsesproblemer, spesielt i applikasjoner med komplekse komponenttrær.
Identifisere unødvendige re-renders
Det første steget i å optimalisere re-renders er å identifisere hvor de skjer. React tilbyr flere verktøy for å hjelpe deg med dette:
1. React Profiler
React Profiler, tilgjengelig i React DevTools-utvidelsen for Chrome og Firefox, lar deg registrere og analysere ytelsen til dine React-komponenter. Det gir innsikt i hvilke komponenter som re-renderer, hvor lang tid de tar å rendere, og hvorfor de re-renderer.
For å bruke Profiler, aktiverer du bare "Record"-knappen i DevTools og samhandler med applikasjonen din. Etter opptak vil Profiler vise et flammediagram som visualiserer komponenttreet og dets renderingstider. Komponenter som tar lang tid å rendere eller som re-renderer ofte, er gode kandidater for optimalisering.
2. Why Did You Render?
"Why Did You Render?" er et bibliotek som patcher React for å varsle deg om potensielt unødvendige re-renders ved å logge de spesifikke propsene som forårsaket re-renderen i konsollen. Dette kan være ekstremt nyttig for å finne rotårsaken til re-renderingsproblemer.
For å bruke "Why Did You Render?", installer det som en utviklingsavhengighet:
npm install @welldone-software/why-did-you-render --save-dev
Importer det deretter i applikasjonens inngangspunkt (f.eks. index.js):
import whyDidYouRender from '@welldone-software/why-did-you-render';
if (process.env.NODE_ENV === 'development') {
whyDidYouRender(React, {
include: [/.*/]
});
}
Denne koden vil aktivere "Why Did You Render?" i utviklingsmodus og logge informasjon om potensielt unødvendige re-renders til konsollen.
3. Bruk av console.log
En enkel, men effektiv, teknikk er å legge til console.log
-setninger i komponentens render
-metode (eller i funksjonskomponentens kropp) for å spore når den re-renderer. Selv om dette er mindre sofistikert enn Profiler eller "Why Did You Render?", kan det raskt fremheve komponenter som re-renderer oftere enn forventet.
Teknikker for å forhindre unødvendige re-renders
Når du har identifisert komponentene som forårsaker ytelsesproblemer, kan du bruke forskjellige teknikker for å forhindre unødvendige re-renders:
1. Memoization
Memoization er en kraftig optimaliseringsteknikk som innebærer å mellomlagre resultatene av dyre funksjonskall og returnere det mellomlagrede resultatet når de samme inputene oppstår igjen. I React kan memoization brukes til å forhindre at komponenter re-renderer hvis deres props ikke har endret seg.
a. React.memo
React.memo
er en høyere-ordens komponent som memoiserer en funksjonell komponent. Den utfører en overfladisk sammenligning av nåværende props med de forrige propsene og re-renderer komponenten bare hvis propsene har endret seg.
Eksempel:
const MyComponent = React.memo(function MyComponent(props) {
return <div>{props.data}</div>;
});
Som standard utfører React.memo
en overfladisk sammenligning av alle props. Du kan gi en tilpasset sammenligningsfunksjon som det andre argumentet til React.memo
for å tilpasse sammenligningslogikken.
const MyComponent = React.memo(function MyComponent(props) {
return <div>{props.data}</div>;
}, (prevProps, nextProps) => {
// Returner true hvis props er like, false hvis de er forskjellige
return prevProps.data === nextProps.data;
});
b. useMemo
useMemo
er en React hook som memoiserer resultatet av en beregning. Den tar en funksjon og en array av avhengigheter som argumenter. Funksjonen utføres bare på nytt når en av avhengighetene endres, og det memoiserte resultatet returneres ved påfølgende renders.
useMemo
er spesielt nyttig for å memorisere dyre beregninger eller for å skape stabile referanser til objekter eller funksjoner som sendes som props til barnekomponenter.
Eksempel:
const memoizedValue = useMemo(() => {
// Utfør en dyr beregning her
return computeExpensiveValue(a, b);
}, [a, b]);
2. PureComponent
PureComponent
er en baseklasse for React-komponenter som implementerer en overfladisk sammenligning av props og state i sin shouldComponentUpdate
-metode. Hvis props og state ikke har endret seg, vil komponenten ikke re-rendere.
PureComponent
er et godt valg for komponenter som kun avhenger av sine props og state for rendering og ikke er avhengige av context eller andre eksterne faktorer.
Eksempel:
class MyComponent extends React.PureComponent {
render() {
return <div>{this.props.data}</div>;
}
}
Viktig merknad: PureComponent
og React.memo
utfører overfladiske sammenligninger. Dette betyr at de bare sammenligner referansene til objekter og arrays, ikke innholdet deres. Hvis dine props eller state inneholder nestede objekter eller arrays, kan det hende du må bruke teknikker som immutabilitet for å sikre at endringer blir oppdaget korrekt.
3. shouldComponentUpdate
Livssyklusmetoden shouldComponentUpdate
lar deg manuelt kontrollere om en komponent skal re-rendere. Denne metoden mottar neste props og neste state som argumenter og skal returnere true
hvis komponenten skal re-rendere, eller false
hvis den ikke skal det.
Selv om shouldComponentUpdate
gir mest kontroll over re-rendering, krever det også mest manuell innsats. Du må nøye sammenligne de relevante props og state for å avgjøre om en re-render er nødvendig.
Eksempel:
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// Sammenlign props og state her
return nextProps.data !== this.props.data || nextState.count !== this.state.count;
}
render() {
return <div>{this.props.data}</div>;
}
}
Advarsel: Feil implementering av shouldComponentUpdate
kan føre til uventet oppførsel og feil. Sørg for at sammenligningslogikken din er grundig og tar hensyn til alle relevante faktorer.
4. useCallback
useCallback
er en React hook som memoiserer en funksjonsdefinisjon. Den tar en funksjon og en array av avhengigheter som argumenter. Funksjonen blir bare redefinert når en av avhengighetene endres, og den memoiserte funksjonen returneres ved påfølgende renders.
useCallback
er spesielt nyttig for å sende funksjoner som props til barnekomponenter som bruker React.memo
eller PureComponent
. Ved å memorisere funksjonen kan du forhindre at barnekomponenten re-renderer unødvendig når foreldrekomponenten re-renderer.
Eksempel:
const handleClick = useCallback(() => {
// Håndter klikk-hendelse
console.log('Clicked!');
}, []);
5. Immutabilitet
Immutabilitet er et programmeringskonsept som innebærer å behandle data som uforanderlige, noe som betyr at de ikke kan endres etter at de er opprettet. Når man jobber med uforanderlige data, resulterer eventuelle modifikasjoner i opprettelsen av en ny datastruktur i stedet for å endre den eksisterende.
Immutabilitet er avgjørende for å optimalisere React re-renders fordi det lar React enkelt oppdage endringer i props og state ved hjelp av overfladiske sammenligninger. Hvis du endrer et objekt eller en array direkte, vil ikke React kunne oppdage endringen fordi referansen til objektet eller arrayen forblir den samme.
Du kan bruke biblioteker som Immutable.js eller Immer for å jobbe med uforanderlige data i React. Disse bibliotekene tilbyr datastrukturer og funksjoner som gjør det enklere å lage og manipulere uforanderlige data.
Eksempel med Immer:
import { useImmer } from 'use-immer';
function MyComponent() {
const [data, setData] = useImmer({
name: 'John',
age: 30
});
const updateName = () => {
setData(draft => {
draft.name = 'Jane';
});
};
return (
<div>
<p>Name: {data.name}</p>
<button onClick={updateName}>Update Name</button>
</div>
);
}
6. Kodedeling og lat lasting (Code Splitting and Lazy Loading)
Kodedeling er en teknikk som innebærer å dele applikasjonens kode i mindre biter som kan lastes ved behov. Dette kan betydelig forbedre den innledende lastetiden for applikasjonen din, ettersom nettleseren bare trenger å laste ned koden som er nødvendig for den gjeldende visningen.
React har innebygd støtte for kodedeling ved hjelp av React.lazy
-funksjonen og Suspense
-komponenten. React.lazy
lar deg importere komponenter dynamisk, mens Suspense
lar deg vise et reserve-UI mens komponenten lastes.
Eksempel:
import React, { Suspense } from 'react';
const MyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
);
}
7. Effektiv bruk av Keys
Når du renderer lister med elementer i React, er det avgjørende å gi unike nøkler (keys) til hvert element. Nøkler hjelper React med å identifisere hvilke elementer som har endret seg, blitt lagt til eller blitt fjernet, slik at DOM-en kan oppdateres effektivt.
Unngå å bruke array-indekser som nøkler, da de kan endre seg når rekkefølgen på elementene i arrayen endres, noe som fører til unødvendige re-renders. Bruk i stedet en unik identifikator for hvert element, for eksempel en ID fra en database eller en generert UUID.
8. Optimalisere bruken av Context
React Context tilbyr en måte å dele data mellom komponenter på uten å eksplisitt sende props gjennom hvert nivå i komponenttreet. Imidlertid kan overdreven bruk av Context føre til ytelsesproblemer, ettersom enhver komponent som konsumerer en Context vil re-rendere hver gang Context-verdien endres.
For å optimalisere bruken av Context, bør du vurdere disse strategiene:
- Bruk flere, mindre Contexts: I stedet for å bruke en enkelt, stor Context for å lagre all applikasjonsdata, del den opp i mindre, mer fokuserte Contexts. Dette vil redusere antall komponenter som re-renderer når en spesifikk Context-verdi endres.
- Memoize Context-verdier: Bruk
useMemo
til å memorisere verdiene som leveres av Context-provideren. Dette vil forhindre unødvendige re-renders av Context-konsumenter hvis verdiene faktisk ikke har endret seg. - Vurder alternativer til Context: I noen tilfeller kan andre tilstandsstyringsløsninger som Redux eller Zustand være mer passende enn Context, spesielt for komplekse applikasjoner med et stort antall komponenter og hyppige tilstandsoppdateringer.
Internasjonale hensyn
Når du optimaliserer React-applikasjoner for et globalt publikum, er det viktig å ta hensyn til følgende faktorer:
- Varierende nettverkshastigheter: Brukere i forskjellige regioner kan ha vidt forskjellige nettverkshastigheter. Optimaliser applikasjonen din for å minimere mengden data som må lastes ned og overføres over nettverket. Vurder å bruke teknikker som bildeoptimalisering, kodedeling og lat lasting.
- Enhetskapasiteter: Brukere kan få tilgang til applikasjonen din på en rekke enheter, fra avanserte smarttelefoner til eldre, mindre kraftige enheter. Optimaliser applikasjonen din for å fungere godt på en rekke enheter. Vurder å bruke teknikker som responsivt design, adaptive bilder og ytelsesprofilering.
- Lokalisering: Hvis applikasjonen din er lokalisert for flere språk, må du sørge for at lokaliseringsprosessen ikke introduserer ytelsesflaskehalser. Bruk effektive lokaliseringsbiblioteker og unngå å hardkode tekststrenger direkte i komponentene dine.
Eksempler fra den virkelige verden
La oss se på noen eksempler fra den virkelige verden på hvordan disse optimaliseringsteknikkene kan brukes:
1. Produktliste i en nettbutikk
Se for deg en nettbutikk med en produktside som viser hundrevis av produkter. Hvert produkt vises som en separat komponent.
Uten optimalisering ville alle produktkomponentene re-rendere hver gang brukeren filtrerer eller sorterer produktlisten, noe som fører til en treg og hakkete opplevelse. For å optimalisere dette kan du bruke React.memo
til å memorisere produktkomponentene, og dermed sikre at de bare re-renderer når deres props (f.eks. produktnavn, pris, bilde) endres.
2. Feed i sosiale medier
En feed i sosiale medier viser vanligvis en liste over innlegg, hver med kommentarer, likes og andre interaktive elementer. Å re-rendere hele feeden hver gang en bruker liker et innlegg eller legger til en kommentar ville være ineffektivt.
For å optimalisere dette kan du bruke useCallback
til å memorisere hendelseshåndtererne for å like og kommentere innlegg. Dette vil forhindre at innleggskomponentene re-renderer unødvendig når disse hendelseshåndtererne utløses.
3. Datavisualiserings-dashboard
Et datavisualiserings-dashboard viser ofte komplekse diagrammer og grafer som oppdateres hyppig med nye data. Å re-rendere disse diagrammene hver gang dataene endres, kan være beregningsmessig dyrt.
For å optimalisere dette kan du bruke useMemo
til å memorisere diagramdataene og bare re-rendere diagrammene når de memoiserte dataene endres. Dette vil redusere antall re-renders betydelig og forbedre den generelle ytelsen til dashboardet.
Beste praksis
Her er noen beste praksiser du bør huske på når du optimaliserer React re-renders:
- Profiler applikasjonen din: Bruk React Profiler eller "Why Did You Render?" for å identifisere komponenter som forårsaker ytelsesproblemer.
- Start med det enkleste: Fokuser på å optimalisere komponentene som re-renderer oftest eller tar lengst tid å rendere.
- Bruk memoization med omhu: Ikke memoriser hver eneste komponent, da memoization i seg selv har en kostnad. Bare memoriser komponenter som faktisk forårsaker ytelsesproblemer.
- Bruk immutabilitet: Bruk uforanderlige datastrukturer for å gjøre det enklere for React å oppdage endringer i props og state.
- Hold komponentene små og fokuserte: Mindre, mer fokuserte komponenter er enklere å optimalisere og vedlikeholde.
- Test optimaliseringene dine: Etter å ha brukt optimaliseringsteknikker, test applikasjonen din grundig for å sikre at optimaliseringene har ønsket effekt og ikke har introdusert nye feil.
Konklusjon
Å forhindre unødvendige re-renders er avgjørende for å optimalisere ytelsen til React-applikasjoner. Ved å forstå hvordan Reacts renderingsprosess fungerer og ved å bruke teknikkene beskrevet i denne guiden, kan du betydelig forbedre responsen og effektiviteten til applikasjonene dine, og dermed gi en bedre brukeropplevelse for brukere over hele verden. Husk å profilere applikasjonen din, identifisere komponentene som forårsaker ytelsesproblemer, og bruke de riktige optimaliseringsteknikkene for å løse disse problemene. Ved å følge disse beste praksisene kan du sikre at React-applikasjonene dine er raske, effektive og skalerbare, uavhengig av kompleksiteten eller størrelsen på kodebasen din.