La tua app è sempre più grande e fa sempre più cose. Su iOS l’app è molto veloce mentre su Android l’app è molto lenta ed a volte si blocca. In questo articolo scopriamo come migliorare le performance di un’app React Native facendo in modo che non si blocchi e che risponda sempre velocemente al tocco dell’utente.
Come migliorare le performance di un’app React Native
Non conosci React Native o hai bisogno di un ripasso? Inizia da questa guida.
Per prima cosa è molto importante ricordarsi che per testare le performance di un’app bisognerebbe seguire questi accorgimenti:
- Provare l’app in release (su Android basta seguire questa guida) o almeno disabilitare il flag __DEV__ (su Android basta fare il run dell’app, far comparire il menù, selezionare “Dev Settings” e togliere il flag da “JS Dev Mode”)
- Per quanto riguarda iOS è sufficiente utilizzare il simulatore, davvero molto performante
- Con Android è meglio invece usare un dispositivo fisico poiché gli emulatori stressano molto il PC ed eventuali rallentamenti potrebbero essere dovuti non dall’app, bensì dal processore occupato in altre operazioni o da un utilizzo eccessivo della CPU
Se la tua app è ancora lenta, specialmente su Android, e risponde lentamente ai tocchi dell’utente è possibile che stai facendo troppe operazioni e che il thread JavaScript sia in sovraccarico.
Tra i problemi di performance riconosciuti ci sono senza dubbio quelli relativi all’utilizzo di react-navigation. Bisogna ricordarsi infatti che React Native esegue tutte le operazioni logiche, ma anche quelle grafiche come le animazioni, sul thread JavaScript e quindi quando si vuole navigare in una nuova voce di menù il thread è occupato nel:
- Mostrare l’animazione di cambio pagina
- Renderizzare i vari componenti
- Eseguire la logica definita in fase di ComponentWillMount e ComponentDidMount
Fin quando i componenti da renderizzare non sono pronti React Navigation (così come altre librerie) rimane in stallo dando la sensazione di avere un’app bloccata e dando la possibilità all’utente di provare più volte a premere un pulsante o una voce di menù per “cambiare route”.
Nota bene: In questa guida impari come migliorare le performance di un’app React Native, quindi non parliamo approfonditamente di React Navigation. Lo stesso discorso infatti si applica ad animazioni lente, frame che saltano, modali che si aprono a scatto, ecc.
Come risolvere il problema?
Dando per scontato che vogliamo certamente mostrare l’animazione di React Navigation (e simili) per il cambio pagina e dicendo che questa operazione non dovrebbe occupare troppo il thread JavaScript, ci concentriamo invece sui componenti da renderizzare e sulla logica definita in fase di mounting.
Le pagine più lente da aprire sono:
- Quelle che eseguono tante operazioni in fase di mounting: chiamano endpoint, leggono dal database, elaborano dati
- Quelle che sono composte da componenti che renderizzano una grande quantità di elementi
- Le pagine che renderizzano un componente che viene renderizzato nuovamente non appena le operazioni in componentDidMount sono andate a buon fine (vedi per esempio il render di una lista di profili che viene mostrata prima vuota con scritto “Non ci sono profili da mostrare” e poi, una volta eseguito il fetch dei dati, viene popolata con i profili)
Per capire come migliorare le performance di un’app React Native puoi seguire i consigli che vengono dati direttamente dal team a questo indirizzo ed anche ascoltare quanto ho io da consigliarti basandomi sull’esperienza personale:
- Esegui il render di un componente solo quando i dati sono pronti (mostra per esempio l’header della tua pagina e un overlay con un ActivityIndicator, vedi questo componente. Quando i dati sono pronti esegui il render del component che li sa mostrare. Il problema maggiore è dovuto a componenti che sono a loro volta composti da tanti altri componenti. In alcuni casi il render di uno potrebbe scatenare un nuovo render di tutti gli altri)
- Importa solo le dipendenze necessarie e cerca di fare pulizia del tuo codice rimuovendo tutto ciò che è superfluo o rappresenta un refuso
- Aiuta il thread JavaScript ad eseguire tutte le operazione correttamente senza sovraccaricarlo
Aiuta il thread JavaScript ad eseguire tutte le operazione correttamente senza sovraccaricarlo, ma come faccio?
Per prima cosa crea un file threadHelper.js e metti al suo interno questo codice:
import {InteractionManager} from 'react-native'; export default class ThreadHelper { /** * Viene risolta la promise non appena il prossimo frame è disponibile */ nextFrame() { return new Promise((resolve, reject) => { requestAnimationFrame(() => { resolve(); }); }) } /** * Invece di andare a riempire la coda del thread JS cerchiamo di non sovracaricarlo eseguendo la callback dopo le interactions, dunque quando l'Animation Frame ha fatto il suo dovere * @param {function} callback - codice da eseguire quando il JS thread è libero */ runWhenThreadIsReady(callback) { InteractionManager.runAfterInteractions(async () => { await this.nextFrame(); callback(); }); } }
Da adesso in poi tutte le operazioni pesanti e, specialmente, tutte quelle operazioni che un componente esegue al componentDidMount falle eseguire passando una funzione a runWhenThreadIsReady. Un esempio? Eccolo quì:
componentDidMount() { this.setupData(); } setupData() { this.threadHelper.runWhenThreadIsReady(() => { //Operazioni varie var cars = this.service.getCars(...); cars.forEach((car) => { //elabora cars.. }); this.setState({ cars: cars, isLoading: false }); }); }
Il componente appena visto deve effettuare un setup dei dati nel componentDidMount (per esempio recuperare delle auto, elaborare i vari oggetti, dunque impostare lo state). Essendo delle operazioni pesanti (magari il service legge dal database locale migliaia di righe che poi vengono elaborate, ecc., ecc.) eseguiamo il tutto solo quando il thread JavaScript è pronto.
Passiamo infatti le operazioni da svolgere come funzione a runWhenThreadIsReady di threadHelper. Questa funzione viene eseguita solo quando:
- InteractionManager.runAfterInteractions risponde
- requestAnimationFrame viene risolto
Cosa sono InteractionManager.runAfterInteractions e requestAnimationFrame?
L’interactionManager permette di eseguire lunghe operazioni dopo che animazioni ed interazioni dell’utente vengono completate (vedi quì). Ciò sta ad indicare che quando entriamo in una pagina viene rispettato il touch dell’utente e qualsiasi altra interazione e dunque anche le animazioni (pagina che si apre, una modale che deve comparire, ecc.). Una volta eseguite interazioni ed animazioni il thread JavaScript si occupa delle operazioni che gli abbiamo chiesto di eseguire.
Le operazioni che nel nostro caso chiediamo di eseguire sono requestAnimationFrame e l’esecuzione della callback passata come parametro. RequestAnimationFrame(fn), da non confondere con setTimeout, prende come parametro una funzione che viene eseguita non appena tutti i frame dell’app sono stati eseguiti (vedi a questo indirizzo e quì).
Ciò vuol dire che nel nostro caso il setup dei dati avverrà solo quando il thread JavaScript è in grado di non bloccare l’applicazione (interazioni e animazioni rispettate) e di non tagliare i frame (quindi animazioni ben visibili, ActivityIndicator che si muovono in maniera lineare).
Come migliorare le performance di un’app React Native
Direi che per questa volta è tutto. Leggiti bene la guida sulle performance di React Native e cerca di scrivere un codice pulito. Ci sono accorgimenti che dovrebbero essere presi sempre, anche se stai utilizzando ReactJS. Cerca di non far rimanere mai l’utente fermo nell’osservare una pagina (web o di un’app) che rimane bianca o bloccata. Piuttosto mostra un caricamento o cerca di suddividere la pagina in blocchi che vengono caricati di volta in volta.
Nei riguardi di React Native utilizza liste virtuali, come la FlatList, e cerca sempre di far sì che l’interazione dell’utente non risulti mai macchinosa.
Se ti è piaciuto questo articolo ed hai imparato come migliorare le performance di un’app React Native potrebbe interessarti anche:
Infine non ti dimenticare alcune risorse importanti:
- Il libro Learning React Native 2nd Edition (O’Reilly)
- Il corso Udemy: The Complete React Native and Redux Course
- Il corso Udemy: React Native: The Practical Guide
Se vuoi rimanere aggiornato sugli articoli del blog ti consiglio di iscriverti alla newsletter. Mando da 1 a 4 mail al mese e normalmente invio risorse gratuite e riservate solo agli iscritti. Invio anche la lista degli articoli di maggiore impatto, come questo. Se non troverai gli articoli potrai recuperarli dalla mail in questo modo ?
Per dubbi o domande non esitare a scrivermi nei commenti ?
Se ti è piaciuto l’articolo seguimi su Facebook e Twitter oppure rimani sempre aggiornato con la newsletter (da 1 a 4 mail al mese!).