Componenti efficienti di reazione: una guida per ottimizzare le prestazioni di reazione

Pubblicato: 2022-03-11

Dalla sua introduzione, React ha cambiato il modo in cui gli sviluppatori front-end pensano alla creazione di app Web. Con il DOM virtuale, React rende gli aggiornamenti dell'interfaccia utente più efficienti che mai, rendendo la tua app web più scattante. Ma perché le app Web React di dimensioni moderate tendono ancora a funzionare male?

Bene, l'indizio è in come stai usando React.

Una moderna libreria front-end come React non rende magicamente la tua app più veloce. Richiede allo sviluppatore di capire come funziona React e come vivono i componenti attraverso le varie fasi del ciclo di vita dei componenti.

Con React, puoi ottenere molti dei miglioramenti delle prestazioni che ha da offrire misurando e ottimizzando come e quando i tuoi componenti vengono renderizzati. Inoltre, React fornisce solo gli strumenti e le funzionalità necessari per semplificare tutto questo.

Velocizza la tua app React ottimizzando il processo di rendering delle differenze di rendering dei tuoi componenti.

In questo tutorial di React imparerai come misurare le prestazioni dei tuoi componenti React e ottimizzarli per creare un'app Web React molto più performante. Imparerai anche come alcune best practice JavaScript aiutano anche a fare in modo che la tua app Web React offra un'esperienza utente molto più fluida.

Come funziona la reazione?

Prima di approfondire le tecniche di ottimizzazione, dobbiamo avere una migliore comprensione di come funziona React.

Al centro dello sviluppo di React, hai la semplice ed ovvia sintassi JSX e la capacità di React di creare e confrontare DOM virtuali. Dalla sua uscita, React ha influenzato molte altre librerie front-end. Anche le biblioteche come Vue.js si basano sull'idea dei DOM virtuali.

Ecco come funziona React:

Ogni applicazione React inizia con un componente radice ed è composta da molti componenti in una formazione ad albero. I componenti in React sono "funzioni" che rendono l'interfaccia utente basata sui dati (props e stato) che riceve.

Possiamo simboleggiare questo come F .

 UI = F(data)

Gli utenti interagiscono con l'interfaccia utente e modificano i dati. Sia che l'interazione comporti il ​​clic su un pulsante, il tocco su un'immagine, il trascinamento di elementi dell'elenco, le richieste AJAX che invocano API, ecc., tutte queste interazioni cambiano solo i dati. Non fanno mai cambiare direttamente l'interfaccia utente.

Qui, i dati sono tutto ciò che definisce lo stato dell'applicazione web e non solo ciò che hai memorizzato nel tuo database. Anche bit di stati front-end (ad esempio, quale scheda è attualmente selezionata o se una casella di controllo è attualmente selezionata) fanno parte di questi dati.

Ogni volta che si verifica un cambiamento in questi dati, React utilizza le funzioni dei componenti per eseguire nuovamente il rendering dell'interfaccia utente, ma solo virtualmente:

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

React calcola le differenze tra l'interfaccia utente corrente e la nuova interfaccia utente applicando un algoritmo di confronto sulle due versioni del suo DOM virtuale.

 Changes = Diff(UI1, UI2)

React procede quindi ad applicare solo le modifiche dell'interfaccia utente all'interfaccia utente reale sul browser.

Quando i dati associati a un componente cambiano, React determina se è necessario un aggiornamento DOM effettivo. Ciò consente a React di evitare operazioni di manipolazione DOM potenzialmente costose nel browser, come la creazione di nodi DOM e l'accesso a quelli esistenti oltre la necessità.

Questa ripetuta differenziazione e rendering dei componenti può essere una delle principali fonti di problemi di prestazioni di React in qualsiasi app React. La creazione di un'app React in cui l'algoritmo di diffing non riesce a riconciliarsi in modo efficace, causando il rendering ripetuto dell'intera app può comportare un'esperienza frustrantemente lenta.

Da dove iniziare a ottimizzare?

Ma cos'è esattamente che ottimizziamo?

Vedete, durante il processo di rendering iniziale, React costruisce un albero DOM come questo:

Un DOM virtuale di componenti React

Data una parte delle modifiche ai dati, ciò che vogliamo che React faccia è eseguire nuovamente il rendering solo dei componenti che sono direttamente interessati dalla modifica (ed eventualmente saltare anche il processo diff per il resto dei componenti):

Reagire rendendo un numero ottimale di componenti

Tuttavia, ciò che React finisce per fare è:

Reagisci sprecando risorse rendendo tutti i componenti

Nell'immagine sopra, tutti i nodi gialli sono renderizzati e differenziati, con conseguente spreco di tempo/risorse di calcolo. È qui che dedicheremo principalmente i nostri sforzi di ottimizzazione. Configurare ogni componente per rendere-diff solo quando è necessario ci consentirà di recuperare questi cicli di CPU sprecati.

Gli sviluppatori della libreria React hanno preso in considerazione questo aspetto e ci hanno fornito un hook per fare proprio questo: una funzione che ci permette di dire a React quando va bene saltare il rendering di un componente.

Misurare in primo luogo

Come dice Rob Pike in modo piuttosto elegante come una delle sue regole di programmazione:

Misurare. Non sintonizzarti sulla velocità finché non hai misurato, e anche allora non farlo a meno che una parte del codice non sopraffà il resto.

Non iniziare a ottimizzare il codice che ritieni possa rallentare la tua app. Invece, lascia che gli strumenti di misurazione delle prestazioni di React ti guidino attraverso il percorso.

React ha uno strumento potente proprio per questo. Utilizzando la libreria react-addons-perf puoi ottenere una panoramica delle prestazioni complessive della tua app.

L'utilizzo è molto semplice:

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

Questo stamperà una tabella con la quantità di componenti di tempo sprecati nel rendering.

Tabella dei componenti che perdono tempo nel rendering

La libreria fornisce altre funzioni che consentono di stampare separatamente diversi aspetti del tempo perso (ad esempio, utilizzando le printInclusive() o printExclusive() ), o anche stampare le operazioni di manipolazione del DOM (usando la funzione printOperations() ).

Fare un passo avanti nel benchmarking

Se sei una persona visiva, allora react-perf-tool è proprio ciò di cui hai bisogno.

react-perf-tool è basato sulla libreria react-addons-perf . Ti offre un modo più visivo per eseguire il debug delle prestazioni della tua app React. Utilizza la libreria sottostante per ottenere misurazioni e quindi visualizzarle come grafici.

Una visualizzazione dei componenti che perdono tempo nel rendering

Il più delle volte, questo è un modo molto più conveniente per individuare i colli di bottiglia. Puoi usarlo facilmente aggiungendolo come componente alla tua applicazione.

Dovrebbe React aggiornare il componente?

Per impostazione predefinita, React eseguirà, renderà il DOM virtuale e confronterà la differenza per ogni componente nell'albero per qualsiasi cambiamento nei suoi prop o stato. Ma questo ovviamente non è ragionevole.

Man mano che la tua app cresce, il tentativo di eseguire nuovamente il rendering e confrontare l'intero DOM virtuale a ogni azione alla fine rallenterà.

React fornisce allo sviluppatore un modo semplice per indicare se un componente necessita di un nuovo rendering. È qui che entra in gioco il metodo shouldComponentUpdate .

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

Quando questa funzione restituisce true per qualsiasi componente, consente di attivare il processo di rendering delle differenze.

Questo ti dà un modo semplice per controllare il processo di render-diff. Ogni volta che è necessario impedire il rendering di un componente, è sufficiente restituire false dalla funzione. All'interno della funzione, puoi confrontare il set di prop e lo stato corrente e successivo per determinare se è necessario un nuovo rendering:

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

Utilizzo di un React.PureComponent

Per facilitare e automatizzare un po' questa tecnica di ottimizzazione, React fornisce ciò che è noto come componente "puro". Un React.PureComponent è esattamente come un React.Component che implementa una funzione shouldComponentUpdate() con un confronto superficiale e di stato.

Un React.PureComponent è più o meno equivalente a questo:

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

Poiché esegue solo un confronto superficiale, potresti trovarlo utile solo quando:

  • I tuoi oggetti di scena o stati contengono dati primitivi.
  • I tuoi oggetti di scena e stati hanno dati complessi, ma sai quando chiamare forceUpdate() per aggiornare il tuo componente.

Rendere i dati immutabili

E se potessi usare un React.PureComponent ma avere comunque un modo efficiente per sapere quando eventuali oggetti di scena o stati complessi sono cambiati automaticamente? È qui che le strutture di dati immutabili semplificano la vita.

L'idea alla base dell'utilizzo di strutture di dati immutabili è semplice. Ogni volta che un oggetto contenente dati complessi cambia, invece di apportare le modifiche in quell'oggetto, crea una copia di quell'oggetto con le modifiche. Ciò rende il rilevamento delle modifiche nei dati semplice come confrontare il riferimento dei due oggetti.

Puoi usare Object.assign o _.extend (da Underscore.js o Lodash):

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

Ancora meglio, puoi usare una libreria che fornisce strutture di dati immutabili:

 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);

Qui, Immutable.Map è fornito dalla libreria Immutable.js.

Ogni volta che una mappa viene aggiornata con il relativo metodo set , viene restituita una nuova mappa solo se l'operazione set ha modificato il valore sottostante. In caso contrario, viene restituita la stessa mappa.

Puoi saperne di più sull'utilizzo di strutture di dati immutabili qui.

Altre tecniche di ottimizzazione delle app React

Utilizzo della build di produzione

Quando sviluppi un'app React, ti vengono presentati avvisi e messaggi di errore davvero utili. Questi rendono l'identificazione di bug e problemi durante lo sviluppo un piacere. Ma hanno un costo in termini di prestazioni.

Se esamini il codice sorgente di React, vedrai molti controlli if (process.env.NODE_ENV != 'production') . Questi blocchi di codice che React sta eseguendo nel tuo ambiente di sviluppo non sono necessari all'utente finale. Per gli ambienti di produzione, tutto questo codice non necessario può essere eliminato.

Se hai avviato il tuo progetto usando create-react-app , puoi semplicemente eseguire npm run build per produrre la build di produzione senza questo codice aggiuntivo. Se stai utilizzando Webpack direttamente, puoi eseguire webpack -p (che equivale a webpack --optimize-minimize --define process.env.NODE_ENV="'production'" .

Funzioni di rilegatura in anticipo

È molto comune vedere funzioni legate al contesto del componente all'interno della funzione di rendering. Questo è spesso il caso quando utilizziamo queste funzioni per gestire gli eventi dei componenti figlio.

 // 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)} />

Ciò farà sì che la funzione render() crei una nuova funzione su ogni rendering. Un modo molto migliore per fare lo stesso è:

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

Utilizzo di più file Chunk

Per le app Web React a pagina singola, spesso finiamo per raggruppare tutto il nostro codice JavaScript front-end in un unico file ridotto. Funziona bene per app Web di piccole e medie dimensioni. Ma quando l'app inizia a crescere, la consegna di questo file JavaScript in bundle al browser in sé può diventare un processo che richiede tempo.

Se stai utilizzando Webpack per creare la tua app React, puoi sfruttare le sue capacità di suddivisione del codice per separare il codice dell'app creata in più "blocchi" e consegnarli al browser in base alle esigenze.

Esistono due tipi di suddivisione: suddivisione delle risorse e suddivisione del codice su richiesta.

Con la suddivisione delle risorse, dividi il contenuto delle risorse in più file. Ad esempio, utilizzando CommonsChunkPlugin, puoi estrarre codice comune (come tutte le librerie esterne) in un file "chunk" a parte. Usando ExtractTextWebpackPlugin, puoi estrarre tutto il codice CSS in un file CSS separato.

Questo tipo di divisione aiuterà in due modi. Aiuta il browser a memorizzare nella cache quelle risorse che cambiano meno frequentemente. Aiuterà inoltre il browser a sfruttare il download parallelo per ridurre potenzialmente il tempo di caricamento.

Una caratteristica più notevole di Webpack è la suddivisione del codice su richiesta. Puoi usarlo per dividere il codice in un blocco che può essere caricato su richiesta. Ciò può ridurre il download iniziale, riducendo il tempo necessario per caricare l'app. Il browser può quindi scaricare altri blocchi di codice su richiesta quando necessario dall'applicazione.

Puoi saperne di più sulla suddivisione del codice Webpack qui.

Abilitazione di Gzip sul tuo server web

I file JS del bundle dell'app React sono comunemente molto grandi, quindi per velocizzare il caricamento della pagina web, possiamo abilitare Gzip sul server web (Apache, Nginx, ecc.)

Tutti i browser moderni supportano e negoziano automaticamente la compressione Gzip per le richieste HTTP. L'abilitazione della compressione Gzip può ridurre la dimensione della risposta trasferita fino al 90%, il che può ridurre significativamente la quantità di tempo per scaricare la risorsa, ridurre l'utilizzo dei dati per il client e migliorare il tempo per il primo rendering delle tue pagine.

Controlla la documentazione del tuo server web su come abilitare la compressione:

  • Apache: usa mod_deflate
  • Nginx: usa ngx_http_gzip_module

Utilizzo di Eslint-plugin-react

Dovresti usare ESLint per quasi tutti i progetti JavaScript. La reazione non è diversa.

Con eslint-plugin-react , ti costringerai ad adattarti a molte regole nella programmazione React che possono avvantaggiare il tuo codice a lungo termine ed evitare molti problemi e problemi comuni che si verificano a causa di codice scritto male.

Reagisci di nuovo velocemente alle tue app Web

Per ottenere il massimo da React, è necessario sfruttare i suoi strumenti e le sue tecniche. Le prestazioni di un'app Web React risiedono nella semplicità dei suoi componenti. Sovraccaricare l'algoritmo della differenza di rendering può far funzionare male la tua app in modi frustranti.

Prima di poter ottimizzare la tua app, dovrai capire come funzionano i componenti di React e come vengono renderizzati nel browser. I metodi del ciclo di vita React ti offrono modi per impedire che il tuo componente venga riprodotto inutilmente. Elimina questi colli di bottiglia e avrai le prestazioni dell'app che i tuoi utenti meritano.

Sebbene ci siano più modi per ottimizzare un'app Web React, la messa a punto dei componenti per l'aggiornamento solo quando richiesto produce il miglior miglioramento delle prestazioni.

Come misuri e ottimizzi le prestazioni della tua app web React? Condividilo nei commenti qui sotto.

Correlati: Recupero di dati obsoleti durante la riconvalida con React Hook: una guida