Componenti funzionali Stateful vs. Stateless in React

React è una libreria di front-end JavaScript popolare per la creazione di interfacce utente interattive. React ha una curva di apprendimento relativamente superficiale, che è uno dei motivi per cui sta ricevendo tutta l'attenzione ultimamente. 

Sebbene ci siano molti concetti importanti da coprire, i componenti sono innegabilmente il cuore e l'anima di React. Avere una buona conoscenza dei componenti dovrebbe renderti la vita facile come sviluppatore di React. 

Prerequisiti

Questo tutorial è destinato ai principianti che hanno iniziato a imparare React e hanno bisogno di una migliore visione dei componenti. Inizieremo con i concetti di base dei componenti e passeremo quindi a concetti più impegnativi come i pattern dei componenti e quando utilizzare questi pattern. Sono state trattate diverse classificazioni dei componenti, come componenti di classe e funzionali, componenti stateful vs. stateless e componenti container e presentativi. 

Prima di iniziare, voglio presentarti il ​​frammento di codice che utilizzeremo in questo tutorial. È un semplice contatore costruito con React. Farò riferimento ad alcune parti di questo esempio durante il tutorial. 

Quindi iniziamo. 

Quali sono i componenti?

I componenti sono microentità indipendenti e autosufficienti che descrivono una parte dell'interfaccia utente. L'interfaccia utente di un'applicazione può essere suddivisa in componenti più piccoli in cui ogni componente ha il proprio codice, struttura e API. 

Facebook, ad esempio, ha migliaia di funzionalità integrate interfacciate quando si visualizza la propria applicazione web. Ecco un dato interessante: Facebook comprende 30.000 componenti e il numero è in crescita. L'architettura dei componenti ti consente di pensare a ogni pezzo in modo isolato. Ogni componente può aggiornare tutto nel suo ambito senza preoccuparsi di come influisce sugli altri componenti. 

Se prendiamo l'interfaccia utente di Facebook come esempio, la barra di ricerca sarebbe un buon candidato per un componente. Newsfeed di Facebook farebbe un altro componente (o un componente che ospita molti sottocomponenti). Tutti i metodi e le chiamate AJAX interessati alla barra di ricerca sarebbero all'interno di quel componente.

I componenti sono anche riutilizzabili. Se hai bisogno dello stesso componente in più posti, è facile. Con l'aiuto della sintassi JSX, puoi dichiarare i tuoi componenti ovunque vuoi che appaiano, e il gioco è fatto. 

 
Conteggio attuale: this.state.count
/ * Riutilizzo dei componenti in azione. * /

Puntelli e stato

I componenti necessitano di dati con cui lavorare. Esistono due modi diversi per combinare componenti e dati: o come oggetti di scena o stato. oggetti di scena e stato determinano cosa rende un componente e come si comporta. Iniziamo con gli oggetti di scena.

oggetti di scena

Se i componenti fossero semplici funzioni JavaScript, gli oggetti di scena sarebbero l'input della funzione. Seguendo questa analogia, un componente accetta un input (ciò che chiamiamo oggetti di scena), lo elabora e quindi rende qualche codice JSX.

Sebbene i dati in oggetti di scena siano accessibili a un componente, la filosofia di React è che gli oggetti di scena dovrebbero essere immutabili e dall'alto verso il basso. Ciò significa che un componente genitore può trasmettere tutti i dati che desidera ai propri figli come oggetti di scena, ma il componente figlio non può modificare i suoi oggetti di scena. Quindi, se provi a modificare gli oggetti di scena come ho fatto di seguito, otterrai l'errore "Impossibile assegnare a sola lettura" TypeError.

const Button = (props) => // gli oggetti di scena sono di sola lettura props.count = 21; ...

Stato

Lo stato, d'altra parte, è un oggetto che appartiene al componente in cui è dichiarato. Il suo scopo è limitato al componente corrente. Un componente può inizializzare il suo stato e aggiornarlo quando necessario. Lo stato del componente genitore di solito finisce con l'essere oggetto del componente figlio. Quando lo stato è passato fuori dal campo di applicazione corrente, ci riferiamo ad esso come un oggetto di scena.


Ora che conosciamo le nozioni di base sui componenti, diamo un'occhiata alla classificazione di base dei componenti.

Componenti di classe e componenti funzionali

Un componente React può essere di due tipi: un componente di classe o un componente funzionale. La differenza tra i due è evidente dal loro nome. 

Componenti funzionali

I componenti funzionali sono solo funzioni JavaScript. Prendono un input opzionale che, come ho detto prima, è ciò che chiamiamo oggetti di scena.


Alcuni sviluppatori preferiscono utilizzare le nuove funzioni freccia ES6 per definire i componenti. Le funzioni di freccia sono più compatte e offrono una sintassi concisa per la scrittura di espressioni di funzione. Usando una funzione freccia, possiamo saltare l'uso di due parole chiave, funzione e ritorno, e un paio di parentesi graffe. Con la nuova sintassi, puoi definire un componente in una singola riga come questa. 

const Hello = (name) => (
Ciao, name!
);

Componenti di classe

I componenti di classe offrono più funzionalità e con più funzionalità arriva più bagaglio. La ragione principale per scegliere i componenti di classe rispetto ai componenti funzionali è che possono avere stato.

La sintassi dello stato = count: 1 fa parte della funzione dei campi della classe pubblica. Maggiori informazioni su questo sotto. 

Esistono due modi per creare un componente di classe. Il modo tradizionale è usare React.createClass (). ES6 ha introdotto uno zucchero di sintassi che consente di scrivere classi che si estendono React.Component. Tuttavia, entrambi i metodi sono pensati per fare la stessa cosa. 

I componenti di classe possono esistere anche senza stato. Ecco un esempio di un componente di classe che accetta un elemento di input e esegue il rendering di JSX.

la classe Hello estende React.Component costruttore (oggetti di scena) super (oggetti di scena);  render () return ( 
Ciao oggetti di scena
)

Definiamo un metodo di costruzione che accetta oggetti di scena come input. All'interno del costruttore, chiamiamo super() per tramandare tutto ciò che viene ereditato dalla classe genitore. Ecco alcuni dettagli che potresti aver perso.

Innanzitutto, il costruttore è facoltativo durante la definizione di un componente. Nel caso precedente, il componente non ha uno stato e il costruttore non sembra fare nulla di utile. this.props usato dentro il render () funzionerà indipendentemente dal fatto che il costruttore sia definito o meno. Tuttavia, ecco qualcosa dai documenti ufficiali:

I componenti di classe dovrebbero sempre chiamare il costruttore di base con oggetti di scena.

Come best practice, raccomanderò di utilizzare il costruttore per tutti i componenti di classe.

In secondo luogo, se stai usando un costruttore, devi chiamare super(). Questo non è opzionale e otterrai l'errore di sintassi "Chiamata super () mancante costruttore" altrimenti. 

E il mio ultimo punto riguarda l'uso di super() vs. super (oggetti di scena). super (oggetti di scena) dovrebbe essere usato se hai intenzione di chiamare this.props all'interno del costruttore. Altrimenti, usando super() solo è sufficiente.

Componenti stateful contro componenti stateless

Questo è un altro modo popolare per classificare i componenti. E i criteri per la classificazione sono semplici: i componenti che hanno lo stato e i componenti che non lo fanno. 

Componenti stateful

I componenti stateful sono sempre componenti di classe. Come accennato in precedenza, i componenti stateful hanno uno stato che viene inizializzato nel costruttore. 

// Ecco un estratto dal costruttore di esempi contatore (oggetti di scena) super (oggetti di scena); this.state = count: 0; 

Abbiamo creato un oggetto stato e inizializzato con un conteggio pari a 0. C'è una sintassi alternativa proposta per rendere più semplice questo campo chiamato classe. Non fa ancora parte delle specifiche ECMAScript, ma se si utilizza un transpiler Babel, questa sintassi dovrebbe funzionare fuori dagli schemi.

App classe estende Componente / * // Non richiesta più costruttore () super (); this.state = count: 1 * / state = count: 1; handleCount (value) this.setState ((precState) => (count: prevState.count + value));  render () // omesso per brevità

È possibile evitare di utilizzare completamente il costruttore con questa nuova sintassi.

Ora possiamo accedere allo stato all'interno dei metodi di classe incluso render (). Se li userete dentro render () per visualizzare il valore del conteggio corrente, devi metterlo tra parentesi graffe come segue:

render () return (Conteggio attuale: this.state.count)

Il Questo la parola chiave qui si riferisce all'istanza del componente corrente. 

L'inizializzazione dello stato non è sufficiente, dobbiamo essere in grado di aggiornare lo stato per creare un'applicazione interattiva. Se pensavi che questo avrebbe funzionato, no, non lo farà.

// Modo errato handleCount (valore) this.state.count = this.state.count + value; 

 I componenti di reazione sono dotati di un metodo chiamato setState per l'aggiornamento dello stato. setState accetta un oggetto che contiene il nuovo stato del contare.

// Questo funziona handleCount (value) this.setState (count: this.state.count + value); 

Il setState () accetta un oggetto come input e incrementiamo il valore precedente del conteggio di 1, che funziona come previsto. Tuttavia, c'è un problema. Quando ci sono più chiamate setState che leggono un valore precedente dello stato e scrivono un nuovo valore in esso, potremmo finire con una condizione di competizione. Ciò significa che i risultati finali non corrisponderanno ai valori previsti.

Ecco un esempio che dovrebbe essere chiaro per te. Prova questo nello snippet codesandbox qui sopra.

// Qual è l'output atteso? Provalo nel sandbox del codice. handleCount (valore) this.setState (count: this.state.count + 100); this.setState (count: this.state.count + value); this.setState (count: this.state.count-100); 

Vogliamo setState per incrementare il conteggio di 100, quindi aggiornarlo di 1, e quindi rimuovere quel 100 che è stato aggiunto in precedenza. Se setState esegue la transizione di stato nell'ordine effettivo, otterremo il comportamento previsto. Tuttavia, setState è asincrono e più chiamate setState possono essere raggruppate per migliorare l'esperienza e le prestazioni dell'interfaccia utente. Quindi il codice sopra produce un comportamento diverso da quello che ci aspettiamo.

Pertanto, invece di passare direttamente un oggetto, puoi passare in una funzione di aggiornamento che ha la firma:

(precState, oggetti di scena) => stateChange 

prevState è un riferimento allo stato precedente ed è garantito per essere aggiornato. gli oggetti di scena si riferiscono agli oggetti di scena del componente, e non abbiamo bisogno di oggetti di scena per aggiornare lo stato qui, quindi possiamo ignorarlo. Quindi, possiamo usarlo per l'aggiornamento dello stato ed evitare le condizioni della gara.

// Il modo corretto handleCount (value) this.setState ((prevState) => count: prevState.count +1); 

Il setState () riorganizza il componente e hai un componente stateful funzionante.

Componenti senza stato

Puoi usare una funzione o una classe per creare componenti senza stato. Ma a meno che non sia necessario utilizzare un gancio del ciclo di vita nei componenti, è necessario utilizzare componenti funzionali stateless. Ci sono molti vantaggi se si decide di utilizzare i componenti funzionali stateless qui; sono facili da scrivere, capire e testare, e puoi evitare il Questo parola chiave del tutto. Tuttavia, a partire da React v16, non ci sono vantaggi prestazionali dall'uso di componenti funzionali stateless rispetto ai componenti di classe. 

Il rovescio della medaglia è che non è possibile avere hook di ciclo di vita. Il metodo del ciclo di vita ShouldComponentUpdate () viene spesso utilizzato per ottimizzare le prestazioni e per controllare manualmente ciò che viene rerenderizzato. Non puoi ancora usarlo con i componenti funzionali. Anche i Refs non sono supportati.

Componenti del contenitore e componenti di presentazione

Questo è un altro pattern molto utile durante la scrittura dei componenti. Il vantaggio di questo approccio è che la logica comportamentale è separata dalla logica di presentazione.

Componenti di presentazione

I componenti della presentazione sono accoppiati con la vista o il modo in cui le cose appaiono. Questi componenti accettano oggetti di scena dalla loro controparte contenitore e li rendono. Tutto ciò che ha a che fare con la descrizione dell'interfaccia utente dovrebbe andare qui. 

I componenti della presentazione sono riutilizzabili e dovrebbero rimanere disaccoppiati dal livello comportamentale. Un componente di presentazione riceve i dati e le richiamate esclusivamente tramite oggetti di scena e quando si verifica un evento, come un pulsante premuto, esegue una richiamata al componente contenitore tramite gli oggetti di scena per richiamare un metodo di gestione degli eventi. 

I componenti funzionali dovrebbero essere la prima scelta per scrivere componenti di presentazione a meno che non sia richiesto uno stato. Se un componente di presentazione richiede uno stato, dovrebbe riguardare lo stato dell'interfaccia utente e non i dati effettivi. Il componente di presentazione non interagisce con il negozio Redux o effettua chiamate API. 

Componenti del contenitore

I componenti del contenitore si occuperanno della parte comportamentale. Un componente contenitore indica al componente di presentazione ciò che dovrebbe essere reso utilizzando i puntelli. Non dovrebbe contenere markup e stili DOM limitati. Se stai usando Redux, un componente contenitore contiene il codice che invia un'azione a un negozio. In alternativa, questo è il posto in cui inserire le chiamate API e memorizzare il risultato nello stato del componente. 

La solita struttura è che in alto c'è un componente contenitore che trasmette i dati ai componenti di presentazione figlio come oggetti di scena. Questo funziona per progetti più piccoli; tuttavia, quando il progetto diventa più grande e hai molti componenti intermedi che accettano solo oggetti di scena e li passano ai componenti del bambino, questo diventerà sgradevole e difficile da mantenere. Quando ciò accade, è meglio creare un componente contenitore univoco per il componente foglia e ciò faciliterà il carico sui componenti intermedi.

Quindi cos'è un PureComponent?

Sentirai molto spesso il termine componente puro nei cerchi di React, e poi c'è React.PureComponent. Quando sei nuovo su React, tutto ciò potrebbe sembrare un po 'confuso. Si dice che un componente sia puro se è garantito restituire lo stesso risultato dato gli stessi oggetti di scena e lo stesso stato. Un componente funzionale è un buon esempio di un componente puro perché, dato un input, sai cosa verrà reso. 

const HelloWorld = (name) => ( 
'Ciao $ nome'
);

Le componenti di classe possono essere pure purché i loro sostegni e lo stato siano immutabili. Se si dispone di un componente con un insieme di oggetti di scena e di stato "profondo" immutabile, l'API React ha qualcosa chiamato PureComponent. React.PureComponent è simile a React.Component, ma implementa il ShouldComponentUpdate () metodo un po 'diverso. ShouldComponentUpdate () è invocato prima che qualcosa venga rerenderizzato. Il comportamento predefinito è che restituisce true in modo che qualsiasi modifica allo stato o agli oggetti di scena restituisca il componente.

shouldComponentUpdate (nextProps, nextState) return true; 

Tuttavia, con PureComponent, esegue un confronto superficiale degli oggetti. Comparazione superficiale significa che si confrontano i contenuti immediati degli oggetti invece di confrontare ricorsivamente tutte le coppie chiave / valore dell'oggetto. Quindi vengono confrontati solo i riferimenti agli oggetti e se lo stato / oggetti di scena sono mutati, questo potrebbe non funzionare come previsto. 

React.PureComponent viene utilizzato per ottimizzare le prestazioni e non vi è alcun motivo per cui si dovrebbe considerare l'utilizzo a meno che non si verifichi un qualche tipo di problema di prestazioni. 

Pensieri finali

I componenti funzionali stateless sono più eleganti e di solito sono una buona scelta per costruire i componenti di presentazione. Perché sono solo funzioni, non avrai difficoltà a scrivere e capirle, e inoltre, sono facili da testare. 

Va notato che i componenti funzionali stateless non hanno il vantaggio in termini di ottimizzazione e prestazioni perché non hanno un ShouldComponentUpdate () gancio. Questo potrebbe cambiare nelle versioni future di React, dove i componenti funzionali potrebbero essere ottimizzati per prestazioni migliori. Tuttavia, se non si è critici sulle prestazioni, è necessario attenersi ai componenti funzionali per la vista / presentazione e ai componenti di classe stateful per il contenitore.

Si spera che questo tutorial ti abbia fornito una panoramica di alto livello dell'architettura basata su componenti e di diversi modelli di componenti in React. Cosa ne pensi di questo? Condividili attraverso i commenti.

Negli ultimi due anni, React è cresciuto in popolarità. In effetti, abbiamo un numero di articoli nel mercato Envato che sono disponibili per l'acquisto, la revisione, l'implementazione e così via. Se stai cercando risorse aggiuntive intorno a React, non esitare a verificarle.