in React

React State Updates

Lo state rappresenta uno degli elementi core di un applicazione React. In pratica è la parte che permette di fornire dinamicità alle applicazioni rendendole di fatto differenti rispetto a semplici template html.

Un aggiornamento dello state di un componente comporta il re-render dello stesso componente: solitamente questo tempo di “refresh” è veramente minimo (e spesso impercettibile per l’utente), ma in alcune situazioni può diventare veramente complesso in fase di visualizzazione della UI.

Quello che React ci mette a disposizione è la possibilità di gestire gli updates in modalità batch (raggruppandoli) per minimizzare i render. Inoltre, è stata introdotta la possibilità di definire una priorità e quindi l’ordine di esecuzione.

Batching state updates

Una delle novità che è stata introdotta all’interno di React 18 è la possibilità di gestire automaticamente gli aggiornamenti batch all’interno dell’applicazione. Quando all’interno di un componente cambia lo state, viene effettuato un re-render interno in modo da visualizzare il risultato del cambio di stato. Ovviamente maggiore è il numero di aggiornamenti che vengono effettuati e maggiore è il numero di re-render che verrà eseguito.

Supponiamo di avere un componente ed effettuare al suo interno una serie di cambiamenti di stato. In particolare, di avere uno <span> e di aggiornarne il contenuto:

<span>valore</span>

L’aggiornamento del contenuto dello span prevede una serie di aggiornamenti:

  • valore1
  • valore2
  • valore3
  • valore4
  • valore5
  • valore1

Questo tipo di aggiornamento comporta il re-render del componente per 6 volte. Analizzando l’aggiornamento possiamo notare che si parte da “valore1” e si arriva nuovamente a “valore1”.

In pratica, vengono eseguiti una serie di re-render che non sono necessari. Trattandosi di un semplice span, non genera particolari problemi di performance ma in applicazioni piu grandi questi aggiornamenti intermedi potrebbero determinare tempi di risposta molto alti.

Per risolvere questo particolare problemi, è stato introdotto il batching. React considera una serie di aggiornamenti dello stato di un componente come un unico aggiornamento. Invece di effettuare N aggiornamenti uno di seguito all’altro, vengono raggruppati come se si trattasse di un aggiornamento unico. Il risultato è gli aggiornamenti usano molto meno risorse, ottimizzando la reattività della UI.

Gestione del batching

In React 17 il batching veniva eseguito solo all’interno di funzioni per la gestione di eventi (event handler): ad esempio le funzioni per la gestione dell’ onClick(). Se posizionamo all’interno di un event handler onClick() una serie di aggiornamenti di stato, React eseguirà un batching per eseguire un unico re-render (se necessario).

Esiste però un problema: le chiamate asincrone. Se all’interno del componente vengono effettuate chiamate asincrone che al termine eseguono l’aggiornamento di stato, queste non possono venire eseguite in batch perchè non vengono eseguite direttamente all’interno dell’event handler, ma in chiamate separate. Questo comportamento rappresenta un vero e proprio problema, anche perchè questo tipo di chiamate sono molto frequenti.

Questo tipo di problema è stato risolto in React 18.

React 18, infatti, estende il concetto di batching a tutti gli updates che vengono eseguiti, indifferentemente dalla posizione del codice in cui vengono inseriti.

Ipotizziamo di avere un pulsante che ad ogni click esegue una funziona una funzione che imposta il valore di una variabile ad ogni secondo. E che questa funzione venga eseguita 100 volte (utilizzando setTimeout()).

import * as React from "react";
export default function BatchingUpdates() {
    let [value, setValue] = React.useState("loading...");
  
	function onStart() {
        setTimeout(() => {
       	 for (let i = 0; i < 100; i++) {
        	    setValue('value ${i + 1}');
   				}
			}, 1);
	}
  
  return (
      <div>
              <p>
                  Value: <em>{value}</em>
              </p>
              <button onClick={onStart}>Start</button>
      </div>
   );
}

Per poter analizzare il comportamento possiamo visualizzare il numero di chiamate che vengono effettuate tramite il profiler di chrome. E’ possibile installare il plugin da questo link.

Consideriamo il caso di React 17: siccome le chiamate eseguite all’interno di setTimeout() sono esterne all’event handler non vengono eseguite in unico batch. Venendo eseguite una di seguito all’altra generano una serie di update della UI che non sono necessari, contribuendo al peggiornamento delle prestazioni dell’applicazione.

Consideriamo il caso di React 18: poichè come descritto in precedenza, i batching sono implementati per ogni tipo di chiamata che aggiorna lo state, anche in presenza di chiamate asincrone, verrà effettuato solo un unico render.

React 18 consente di eseguire automaticamente l’organizzazione a batch delle chiamate.

L’unica modifica che si rende necessaria al codice per poter gestire automaticamente gli aggiornamenti in modalità batch è quella relativa alla modalità di render. Partendo ad esempio da:

ReactDOM.render(
	<React.StrictMode>
		<App />
	</React.StrictMode>,
	document.getElementById("root")
);

è necessario modificarlo in

ReactDOM.createRoot(document.getElementById("root")).render(
	<React.StrictMode>
		<App />
	</React.StrictMode>
);

Modificando il render in questo modo, siamo sicuri che React 18 utilizzi il batching all’interno dell’applicazione.

React 18 permette di migliorare notevolmente la gestione dei problemi di performance all’interno delle nostre applicazioni, gestendo internamente i raggruppamenti delle chiamate.

Un altra funzionalità molto importante introdotta in React 18 è la possibilità di gestire un ordine di priorità alle chiamate, indicando in pratica l’ordine in cui dovranno essere eseguite. Di questo argomento parleremo in un prossimo post.