Pagina principale

Daniele Irsuti
frontend developer

State managers: servono ancora?

Gli state manager ci servono ancora. Ricoprono ancora un ruolo, più marginale ma non costituiscono più un elemento di dibattito centrale

Redux è la prima parola che ti viene in mente, non è vero?

Se è così, probabilmente lo hai visto in tutte le salse (o in tutti i middlewares 🧐) e probabilmente sarai anche stufo di usarlo, discuterne o anche solo immaginare di doverti barcamenare tra actions, action creators, reducer e tutte quelle brutte parole.

Spoiler: non parlerò di Redux, lo sfiorerò soltanto. Tipo una carezza. Anzi no, più una pacchetta di incoraggiamento. Bravo Redux, stai buono… non ti muovere eh 🪑.

Ci servono ancora?

Abbiamo la Context API, librerie più versatili e meno verbose… perché accidenti dovrei usare uno state manager e complicarmi la vita? Mi conviene?

La context API

Questa funzionalità può sembrare comoda e semplicissima da usare ma non è così gratuita come può sembrare e più se ne abusa e più il conto alla fine diventa salato.

Va fatta particolare attenzione come la usiamo, perché sappiamo bene tutti cosa succede quando uno state viene cambiato, giusto?

Il compromesso in realtà è accettabile in determinati casi ma è sempre bene rifletterci un attimo.

Il rerender è un comportamento di React che non significa sempre “male” perché è semplicemente è così che funziona la libreria, solo che possiamo e dobbiamo fare il possibile per evitare che ne avvengano troppi - certo, questo è un discorso che dovremmo fare alla fine per evitare ottimizzazioni premature ma è bene saperlo a priori.

Dobbiamo avere piena coscienza che cambiare lo state in un componente posizionato in alto gerarchicamente può causare dei problemi di performance, percepibili dall’utente anche se il suo device è il migliore sul mercato.

Si tratta di fare compromessi: oggi è un compromesso, domani ne sono 2,3, meglio non dormire su questo.

Facendo un esempio pratico:

Ipotizziamo un componente che chiameremo UserProvider (che usa il provider di un context che chiamerò User) e che contenga 2 componenti figli: Navbar e Main.

Immaginiamo che Navbar al suo interno abbia un componente che consumi il context di User.

Ora, che succede se cambio lo stato contenuto all’interno di UserProvider per impostare un nuovo utente?

🔄 Si rirenderizza UserProvider e ProfileAvatar.

Ci piace!

Ma quand'è che si complicano le cose? Ipotizziamo che adesso tutti i componenti figli abbiano leggano qualcosa dal provider (immaginiamo dunque che il provider abbia un oggetto e ogni figlio legge una proprietà specifica).
Se anche solo volessimo cambiare una di quelle proprietà adesso abbiamo un problema: sono legate ad un oggetto.

La referenza dell'oggetto cambia e quindi... tutti i consumer vedono che la referenza cambia e quindi tutti i componenti che leggono il provider si rirenderizzano.

Si potrebbe ottimizzare? Certo... ma la soluzione non è così immediata come sembra.

Usiamo uno state manager?

Premessa: gli state manager utilizzano la context API ma sono ottimizzati per leggere e scrivere dei dati senza incorrere nei problemi che abbiamo visto nell'esempio precedente.

Qualsiasi state manager (Jotai, Recoil, Redux per citarne alcuni) dispone di un provider da utilizzare in alcuni punti della propria applicazione (solitamente a wrapper dell’intera app se usiamo Redux, in cima quindi).

Ma cosa cambia a livello di implementazione?

A differenza di prima, ho scelto di posizionare il provider a lato ma a livello di implementazione è identico al precedente (quindi immaginiamolo a wrapper dei 2 figli Navbar e Main).

Ipotizzando che ad un certo punto in Main, in un componente sotto di esso, potessimo cambiare lo user senza mai consumarlo direttamente. Main subirebbe un rerender?

Sorprendentemente, no.

Ma non c’è nessuna magia in questo.

Ma facciamoci la domanda di prima al contrario: quand’è che un componente React si rirenderizza? 🤔

Ma andiamo avanti. Pensiamo a come accediamo al dato quando usiamo uno state manager (uno qualunque, non per forza quello con la R…).

Gli state manager hanno dei modi particolari per accedere al dato: in genere si usano delle funzioni (o hook) . Queste particolari funzioni hanno il compito di rendere quella dipendenza responsabile del cambiamento della UI, in altre parole li rende stateful.

Quindi se da Main modifichiamo user, l’unico rerender che vedremo nella nostra app avverrà solo in Navbar.

Com'è possibile!?

Il segreto è che gli state manager… non usano state!!! Sconcertante 😨

Essi infatti utilizzano dei pattern a sé stanti che salvano il dato senza passare dagli state di React, nel provider, e lo condividono rendendolo “stateful” solo attraverso le funzioni che ho citato prima.

Quindi si usano, non si usano che si fa?

TLDR: Si usano, se serve accedere a dei dati (che cambiano frequentemente) per tutta l'applicazione.

L’esigenza di oggi è cambiata rispetto a come avveniva non molto tempo fa e mi sembra che ci sia stato un progressivo allontanamento dal concetto di “single source of truth” (che significava perlopiù buttare dentro uno state manager qualsiasi chiamata http, oltre che qualche dato utile all’applicazione).

React Query e simili hanno semplificato di tantissimo l’esperienza di sviluppo con questo tipo di problematica, e quindi il problema si è ridotto all’osso.

Tuttavia in una real world application 🧐 in React può nascere l’esigenza di dover rendere disponibili dei dati su più parti dell’applicazione, ma proprio per un discorso di performance non ci si vuole legare ai context.

Come nell’esempio di prima, dell’user condiviso su un figlio di Navbar, non necessariamente impatta su tutto il componente genitore Navbar. Perché quindi rendere tutta l’applicazione dipendente da user (come nel primissimo esempio) quando possiamo rendere dipendenti da quel dato solo alcune parti?

Gli state manager mirano a risolvere questa problematica: distribuire il dato unicamente dove serve, senza impattare sul resto dei componenti facendo compiere a React meno lavoro possibile.

Link utili

Daniele Irsuti

Note sull'autore

Daniele irsuti è specializzato in applicazioni React, React Native e vanilla. È un appassionato di grafica, psicologia e scrittura