Prima di esaminare i meccanismi che regolano il rendering dell'interfaccia utente di una pagina in React, è utile considerare che, nel tempo, il processo di generazione dell'interfaccia utente si è spostato dal server al client sfruttando sempre più le risorse dei terminali.
In passato, ad ogni interazione, il server doveva rigenerare e inviare completamente tutto il markup della pagina. Dall'introduzione di AJAX possiamo invece richiedere al server la quantità minima di dati necessari ad aggiornare i contenuti e apportare le modifiche alle pagina sfruttando il DOM.
Il processo di "rendering"
Nel gergo di React (e anche dell'HTML), il rendering ha a che fare con la creazione di elementi all'interno del client: è l'atto con cui la libreria crea un nuovo elemento e lo inserisce nel DOM per raffigurarlo nella pagina.
Il termine "rendering" in senso generico si riferisce al concetto di "resa grafica", quindi al "visualizzare qualcosa": ad esempio, nel caso dei programmi 3D si parla del rendering di una scena, mentre nella grafica 2D si parla di tracciare forme e bitmap.
Ma come fa React a sapere qual è l'elemento da creare? È lo sviluppatore a indicarlo, restituendo l'oggetto che lo rappresenta come valore di ritorno in una funzione preposta.
Abbiamo già detto che gli oggetti con cui lavoriamo e a cui ci riferiamo sono artefatti creati tramite funzioni React, ed è proprio la fase di rendering che concretizza tali oggetti trasformandoli in elementi reali del DOM. Durante tale materializzazione, la libreria ha la facoltà di agganciare agli elementi la gestione di eventi interessanti per rispondere alle azioni dell'utente e aggiornare l'interfaccia ripetendo di nuovo il rendering.
Funzione di rendering
Poter visualizzare qualcosa con React dobbiamo approntare una funzione di rendering, ossia una funzione che si occupi di creare e restituire (con la parola chiave return) l'elemento del Virtual DOM da rappresentare come valore di ritorno.
Possiamo assolvere il compito chiamando React.createElement() oppure sfruttando la comoda sintassi JSX; per i componenti più semplici che non prevedono uno stato da gestire, è sufficiente dichiarare una funzione senza imbastire un oggetto strutturato che rappresenti il nostro componente:
var HelloWorld = function () {
return <p>Hello World!</p>;
};
Questa funzione rappresenta un componente React a tutti gli effetti, ed essa indica che il rendering dovrà creare un paragrafo contenente il testo "Hello World!"
.
Per visualizzare concretamente qualcosa nella pagina, dobbiamo sempre ricordarci di chiamare almeno una volta ReactDOM.render(), altrimenti React non eseguirà il rendering e non vedremo nulla.
ReactDOM.render(<HelloWorld />, document.getElementById('container'));
Render, funzioni pure e props
L'esempio appena visto fa uso di una funzione pura (pure function): viene definita in questo modo qualsiasi funzione che produce a ogni chiamata un identico risultato a parità dei valori passati come parametri.
Questo assioma ci dice quindi che possiamo anche supportare parametri nella dichiarazione della funzione, purché ciò che restituiamo sia sempre uguale per se passiamo gli stessi valori alla funzione.
React possiede qualcosa che si avvicina esattamente a questo paradigma: le proprietà (props).
Ecco un esempio di funzione che fa uso delle proprietà, passate come parametro, per creare un componente più complesso ma, di nuovo, "puro":
var ImageLink = function (props) {
return (
<a href={props.href} target="_blank">
<img src={props.src} />
</a>
);
};
ReactDOM.render(
<ImageLink
href="http://www.html.it"
src="http://www.gruppohtml.it/Images/Network/Loghi/logoHTML.jpg" />,
document.getElementById('container')
);
Le funzioni pure devono rispettare un altro dogma: non possono avere cosiddetti side effect, o "effetti collaterali", ovvero non deve modificare lo stato di altri oggetti o intervenire su altre strutture alterando lo stato del sistema nel suo complesso.
L'approccio non potrà quindi essere adottato per definire componenti React che fanno uso dello stato (state): per questi casi sarà sempre possibile ricorrere alla funzione React.createClass() passando come parametro l'oggetto che rappresenta il componente, dotato dei metodi render(), getInitialState() e così via, come abbiamo visto nelle lezioni precedenti.
Valore di ritorno della funzione
Parliamo ora del valore di ritorno della funzione di rendering.
Possiamo includere nella funzione tutta la logica che vogliamo, purché alla fine restituisca un oggetto ReactElement che indichi a React qual è l'elemento da creare per rappresentare il componente nella pagina.
Se esistono condizioni logiche per le quali è necessario non visualizzare alcuna interfaccia, possiamo restituire il valore null: React non andrà a inserire alcun elemento nel DOM della pagina, come se avessimo nascosto il componente e tutto ciò che contiene all'interno.
E se dobbiamo generare più di un elemento? In questo caso, dobbiamo includerlo all'interno di un elemento contenitore, e dobbiamo fare ancora più attenzione a questo problema quando usiamo la sintassi JSX. Ad esempio, esaminiamo il codice che segue:
var Chapter = function (props) {
return (
<h1>{props.title}</h1>
<p>{props.content}</p>
);
};
La restituzione del valore di ritorno è errata, poiché l'espressione tra parentesi include due elementi allo stesso livello, e sappiamo benissimo che il valore di ritorno di qualsiasi funzione JavaScript è uno e uno solo.
Per risolvere il problema, dobbiamo inserire gli elementi all'interno di un <div>
o di un altro elemento equivalente che possa accoglierli. Il codice corretto sarà quindi simile al seguente:
var Chapter = function (props) {
return (
<article>
<h1>{props.title}</h1>
<p>{props.content}</p>
</article>
);
};
In questo caso, il valore di ritorno è un oggetto ReactElement che rappresenta l'elemento HTML5 <article>
e che contiene al suo interno gli elementi figli con il titolo principale e il testo del paragrafo.
Aggiornare il rendering di un elemento
Il rendering di componenti React è un'operazione una tantum: viene eseguita da React quando invochiamo la funzione ReactDOM.render(). Quindi, in assenza di uno stato (state) che possa essere modificato rendendo necessario l'aggiornamento dell'interfaccia, il rendering non viene reiterato.
Ciò non toglie che possiamo comunque aggiornare l'interfaccia, anche utilizzando una funzione pura per il rendering di un componente. È sufficiente chiamare di nuovo la funzione ReactDOM.render() al momento opportuno, ad esempio al verificarsi di un evento di nostro interesse.
Analizziamo il codice che segue:
function tick() {
const element = (
<div>
<h1>Ciao a tutti!</h1>
<h2>Sono le ore {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('container')
);
}
setInterval(tick, 1000);
La funzione tick()
è composta da due parti: la definizione dell'elemento di cui eseguire il rendering espresso tramite la sintassi JSX e la chiamata alla funzione ReactDOM.render() per aggiornare la rappresentazione nella pagina.
Tramite la funzione JavaScript setInterval(), chiamiamo ripetutamente la funzione a intervalli regolari di un secondo: in questo modo, otteniamo una pagina che mantiene aggiornata la visualizzazione dell'ora corrente senza dover ricorrere allo stato.
È bene precisare inoltre che React continua a sfruttare i benefici del Virtual DOM nell'aggiornamento della pagina: ad ogni richiesta di rendering successiva alla prima, React è in grado di determinare che solo la parte del testo che contiene l'ora è l'unica ad essere cambiata, e si limita pertanto ad aggiornare solo quell'elemento agendo sul DOM reale, lasciando invariato tutto il resto.
Nella prossima lezione vedremo come assemblare più componenti tra loro usando le tecniche già viste e gestirne il rendering nel modo appropriato.