React, performance profiling e colli di bottiglia inaspettati
In questo articolo Damiano Salvi riporta un caso d’uso del tool di profiling messo a disposizione dai Chrome DevTools, applicato al componente React Table usato in un progetto.
Componenti React ⚛ performance e profiling
Chiunque abbia l’occasione di sviluppare un applicativo con React, prima o poi si scontra con l’inevitabile constatazione che “qualcosa” è troppo lento.
Colpa di React? Quasi sempre, la risposta è no: il framework offre diversi modi per ottimizzare le performance: nella guida ufficiale, c’è un’intera sezione dedicata a questo tema.
Uno degli strumenti più utili a questo proposito è il Profiler di Chrome, che consente di tracciare il ciclo di vita dei componenti
React Table e profiling: Profiler in azione
Per esigenze di progetto, abbiamo esteso il componente React Table per gestire il caricamento di dati tramite infinite scroll.
Facendo qualche test, però, insieme a Francesco Sacchi notiamo che l’interazione con la tabella non è particolarmente fluida, e che il consumo di CPU è molto alto anche quando l’utente non sta interagendo con la pagina.
Una volta esclusi memory leaks, chiamate REST impreviste, cicli di loop dimenticati nel codice… con Francesco iniziamo a raccogliere informazioni tramite il Profiler.
Isoliamo quindi il componente in un esempio semplice tramite React Styleguidist, e con il Profiler attivo proviamo l’infinite scroll.
Il risultato lascia un po’ perplessi, come si può vedere dall’immagine seguente.
Le aree colorate di arancio, viola e grigio rappresentano i momenti in cui abbiamo attivato lo scroll sulla pagina, causando il caricamento di nuovi dati e l’aggiornamento della tabella: fin qui tutto normale, è dove ci aspettiamo di vedere dell’attività.
Quello che non ci saremmo aspettati, invece, sono tutte le parti colorate in verde nella riga FPS (Frames Per Second), che indicano che il browser sta ridisegnando una parte della pagina, anche quando non stavamo facendo nulla.
L’opzione Paint Flashing e il Repaint. Perché?
Per capire cosa il browser stia facendo, i Dev Tools offrono un altro strumento utile: nelle opzioni di Rendering, basta attivare l’opzione Paint Flashing per vedere colorarsi di verde ciò che il browser sta ridisegnando (immagine seguente).
Con quest’opzione attiva riproviamo ad usare la nostra React table, e vediamo tutto colorarsi di verde: non è un buon segno! O magari va interpretato diversamente, dopotutto si dice “verde speranza”: in fondo, siamo più vicini a trovare la causa del nostro problema di performance.
Dopo aver vagliato diverse ipotesi, cercando di semplificare via via il componente, arriviamo finalmente a restringere il cerchio dei sospettati ad un solo elemento.
Facciamo una prova sostituendolo, e come d’incanto il browser inizia a comportarsi come avremmo voluto.
Finalmente il browser ridisegna solo quello che effettivamente dovrebbe e solo quando ce lo si aspetta.
Stupiti dalla scoperta, con Francesco pensiamo di condividerla con il resto del team, ma ci sorge una domanda: “Qualcun altro al nostro posto avrebbe subito cercato il problema nel punto giusto?”.
Vi proponiamo un piccolo quiz.
Cosa stava rallentando drasticamente le performance?
- Errata implementazione del metodo shouldComponentUpdate( )
- L’encoding errato di alcuni caratteri speciali
- Un ciclo di loop infinito
- Una GIF animata
- Un numero eccessivo di listeners
La risposta è…
….
è…
La risposta corretta è la numero 4: una GIF animata!
A quanto pare, tutti i nostri problemi erano causati dalla piccola barra di loading che mostravamo in cima o in fondo alla pagina durante il caricamento di nuove righe, poiché in realtà questa immagine era applicata come sfondo all’intera tabella (e poi coperta dallo sfondo delle singole celle).
Sostituire questa GIF animata con una statica ci ha permesso di migliorare drasticamente le performance senza cambiare una sola riga di codice.
TL;DR: non usate GIF animate nei vostri componenti se non è necessario.
Riferimenti
Alcuni riferimenti per i framework e i tool utilizzati:
- React
- React Styleguidist
- Componente React Table (v6)
- Screencastify (usato per generare i brevi filmati presenti nell’articolo)