Oggi React è una delle librerie JavaScript più popolari. Può essere utilizzata per creare applicazioni dinamiche e reattive, offre prestazioni migliori e può essere estesa facilmente. La logica sottostante si basa su componenti che possono essere riutilizzati in contesti diversi, riducendo la necessità di scrivere lo stesso codice più volte. In breve, con React è possibile creare applicazioni efficienti e potenti.

Proprio per questo, non c’è mai stato un momento migliore per imparare a creare applicazioni con React.

Tuttavia, senza una solida conoscenza di alcune caratteristiche chiave di JavaScript, sviluppare applicazioni React potrebbe essere difficile o addirittura impossibile.

Per questo abbiamo stilato un elenco di caratteristiche e dei concetti JavaScript che è necessario conoscere prima di iniziare a lavorare con React. Quanto meglio si comprendono questi concetti, tanto più facile sarà costruire applicazioni React professionali.

Detto questo, ecco di cosa parleremo in questo articolo:

JavaScript e ECMAScript

JavaScript è un popolare linguaggio di scripting utilizzato insieme a HTML e CSS per creare pagine web dinamiche. Mentre l’HTML viene utilizzato per creare la struttura di una pagina web e il CSS per creare lo stile e il layout dei suoi elementi, JavaScript è il linguaggio utilizzato per aggiungere comportamento alla pagina, cioè per creare funzionalità e interattività.

Da allora il linguaggio è stato adottato dai principali browser ed è stato scritto un documento per descrivere il modo in cui JavaScript doveva funzionare: lo standard ECMAScript.

A partire dal 2015, ogni anno viene rilasciato un aggiornamento dello standard ECMAScript e quindi ogni anno vengono aggiunte nuove funzionalità a JavaScript.

ECMAScript 2015 è stata la sesta versione dello standard ed è quindi noto anche come ES6. Le versioni successive sono segnate in progressione, quindi ci riferiamo a ECMAScript 2016 come ES7, ECMAScript 2017 come ES8 e così via.

Per via della frequenza con cui vengono aggiunte nuove funzionalità allo standard, alcune potrebbero non essere supportate da tutti i browser. Quindi, come assicurarsi che le ultime funzionalità di JavaScript aggiunte alla propria applicazione JS funzionino come previsto su tutti i browser?

Ci sono tre possibilità:

  1. Aspettare che tutti i principali browser forniscano il supporto per le nuove funzionalità. Ma se avete assolutamente bisogno di quella nuova funzione JS per la vostra applicazione, questa non è una soluzione percorribile.
  2. Utilizzare un Polyfill, che è “un pezzo di codice (di solito JavaScript sul Web) utilizzato per fornire funzionalità moderne su browser più vecchi che non le supportano in modo nativo” (si veda anche mdn web docs).
  3. Utilizzare un transpiler JavaScript come Babel o Traceur, che convertono il codice ECMAScript 2015+ in una versione di JavaScript supportata da tutti i browser.

Dichiarazioni ed Espressioni

Capire la differenza tra dichiarazioni ed espressioni è essenziale per la creazione di applicazioni React. Quindi torniamo per un attimo ai concetti di base della programmazione.

Un programma per computer è un elenco di istruzioni che devono essere eseguite da un computer. Queste istruzioni sono chiamate dichiarazioni.

A differenza delle dichiarazioni, le espressioni sono frammenti di codice che producono un valore. In una dichiarazione, un’espressione è una parte che restituisce un valore e di solito si trova alla destra del segno di uguale.

Mentre:

Le espressioni JavaScript possono essere blocchi o righe di codice che di solito terminano con il punto e virgola o sono racchiuse tra parentesi graffe.

Ecco un semplice esempio di dichiarazione in JavaScript:

document.getElementById("hello").innerHTML = "Hello World!";

La dichiarazione qui sopra scrive "Hello World!" in un elemento DOM con id="hello".

Come abbiamo detto, le espressioni producono un valore o sono esse stesse un valore. Consideriamo il seguente esempio:

msg = document.getElementById("hello").value;

document.getElementById("hello").value è un’espressione in quanto restituisce un valore.

Un altro esempio dovrebbe aiutare a chiarire la differenza tra espressioni e dichiarazioni:

const msg = "Hello World!";
function sayHello( msg ) {
	console.log( msg );
}

In questo esempio,

  • la prima riga è una dichiarazione, dove "Hello World!" è un’espressione,
  • la dichiarazione della funzione è una dichiarazione, mentre il parametro msg passato alla funzione è un’espressione,
  • la riga che stampa il messaggio nella console è una dichiarazione, dove il parametro msg è un’espressione.

Perché le Espressioni Sono Importanti in React

Quando si crea un’applicazione React, è possibile iniettare espressioni JavaScript nel codice JSX. Ad esempio, si può passare una variabile, scrivere un event handler o una condizione. È possibile farlo inserendo il codice JS tra parentesi graffe.

Ad esempio, si può passare una variabile in questo modo:

const Message = () => {
	const name = "Carlo";
	return <p>Welcome {name}!</p>;
}

In breve, le parentesi graffe indicano al transpiler di elaborare il codice racchiuso tra parentesi come codice JS. Tutto ciò che viene prima del tag di apertura <p> e dopo il tag di chiusura </p> è normale codice JavaScript. Tutto ciò che si trova all’interno dei tag di apertura <p> e di chiusura </p> viene elaborato come codice JSX.

Ecco un altro esempio:

const Message = () => {	
	const name = "Ann";
	const heading = <h3>Welcome {name}</h3>;
	return (
		<div>
			{heading}
			<p>This is your dashboard.</p>
		</div>
	);
}

Si può anche passare un oggetto:

render(){			
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22',
		description: 'Content Writer'
	}

	return (
		<div>
			<h2>Welcome {person.name}</h2>
			<img
				className="card"
				src={person.avatar}
				alt={person.name}
			/>
			<p>Description: {person.description}.</p>
		</div>
	);
}

Di seguito è riportato un esempio più completo:

render(){
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22?size=original',
		description: 'Content Writer',
		theme: {
			boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)', width: '200px'
		}
	}

	return (
		<div style={person.theme}>
			<img
				src={person.avatar}
				alt={person.name}
				style={ { width: '100%' } }
			/>
			<div style={ { padding: '2px 16px' } }>
				<h3>{person.name}</h3>
				<p>{person.description}.</p>
			</div>
		</div>
	);
}

Si notino le doppie parentesi graffe negli attributi style degli elementi img e div. Abbiamo usato le doppie parentesi per passare due oggetti che contengono gli stili della scheda e dell’immagine.

Un esempio di biglietto da visita creato con React
Un esempio di biglietto da visita creato con React

In tutti gli esempi precedenti abbiamo inserito espressioni JavaScript in JSX.

Immutabilità in React

La mutabilità e l’immutabilità sono due concetti chiave della programmazione funzionale e orientata agli oggetti.

Immutabilità significa che un valore non può essere modificato dopo che lo si è creato. Mutabilità significa, ovviamente, il contrario.

In Javascript, i valori primitivi sono immutabili: ciò significa che una volta creato un valore primitivo, questo non può essere modificato. Al contrario, gli array e gli oggetti sono mutabili perché le loro proprietà e i loro elementi possono essere modificati senza riassegnare un nuovo valore.

Ci sono diversi motivi per utilizzare oggetti immutabili in JavaScript:

  • Miglioramento delle prestazioni
  • Riduzione del consumo di memoria
  • Sicurezza dei thread
  • Semplificazione del codice e del debug

Seguendo il modello dell’immutabilità, una volta che una variabile o un oggetto viene assegnato, non può essere riassegnato o modificato. Quando si ha bisogno di modificare i dati, è necessario crearne una copia e modificarne il contenuto, lasciando inalterato il contenuto originale.

L’immutabilità è un concetto chiave anche in React.

La documentazione di React afferma che:

Lo stato di un componente di classe è disponibile come this.state. Il campo dello stato deve essere un oggetto. Non modificare direttamente lo stato. Se si desidera cambiare lo stato, invocare setState con il nuovo stato.

Ogni volta che lo stato di un componente cambia, React calcola se renderizzare nuovamente il componente e aggiornare il DOM virtuale. Se React non avesse traccia dello stato precedente, non potrebbe stabilire se renderizzare nuovamente il componente o meno. La documentazione di React ne fornisce un ottimo esempio.

Quali caratteristiche di JavaScript possiamo utilizzare per garantire l’immutabilità dell’oggetto state in React? Scopriamole!

La Dichiarazione delle Variabili

Ci sono tre modi per dichiarare una variabile in JavaScript: var, let, e const.

Le dichiarazioni var esistono fin dall’inizio di JavaScript. Si usano per dichiarare una variabile a livello di funzione o a livello globale, inizializzandola facoltativamente a un valore.

Quando si dichiara una variabile con var, è possibile ridichiararla e aggiornarla sia nello scopo globale che in quello locale. Il seguente codice è consentito:

// Declare a variable
var msg = "Hello!";

// Redeclare the same variable
var msg = "Goodbye!"

// Update the variable
msg = "Hello again!"

Le dichiarazioni var vengono elaborate prima dell’esecuzione di qualsiasi codice. Di conseguenza, dichiarare una variabile in qualsiasi punto del codice equivale a dichiararla all’inizio. Questo comportamento è noto come “hoisting“, o sollevamento.

Vale la pena notare che solo la dichiarazione della variabile viene sollevata, non l’inizializzazione, che avviene solo quando il flusso di controllo raggiunge la dichiarazione di assegnazione. Fino a quel momento, la variabile è undefined:

console.log(msg); // undefined
var msg = "Hello!";
console.log(msg); // Hello!

L’ambito di una var dichiarata in una funzione JS è l’intero corpo della funzione stessa.

Ciò significa che la variabile non è definita a livello di blocco, ma a livello dell’intera funzione. Questo comporta una serie di problemi che possono rendere il codice JavaScript buggato e difficile da mantenere.

Per risolvere questi problemi, ES6 ha introdotto la parola chiave let.

La dichiarazione let dichiara una variabile locale a livello di blocco, inizializzandola facoltativamente a un valore.

Quali sono i vantaggi di let rispetto a var? Eccone alcuni:

  • let dichiara una variabile nell’ambito do scopo del blocco, mentre var dichiara una variabile a livello globale o locale per un’intera funzione, indipendentemente dallo scopo del blocco.
  • Le variabili globali dichiarate con let non sono proprietà dell’oggetto window. Non è possibile accedervi con window.variableName.
  • È possibile accedere a let solo dopo che la sua dichiarazione è stata raggiunta. La variabile non viene inizializzata fino a quando il flusso di controllo non raggiunge la riga di codice in cui è stata dichiarata (le dichiarazioni let sono non-hoisted).
  • La redichiarazione di una variabile con let provoca un SyntaxError.

Dal momento che le variabili dichiarate con var non possono essere scansionate in blocco, se si definisce una variabile con var in un ciclo o all’interno di Una dichiarazione if, è possibile accedervi dall’esterno del blocco e questo può generare errori nel codice.

Il codice del primo esempio viene eseguito senza errori. Ora sostituite var con let nel blocco di codice visto sopra:

console.log(msg);
let msg = "Hello!";
console.log(msg);

Nel secondo esempio, l’utilizzo di let al posto di var produce un Uncaught ReferenceError:

Uncaught ReferenceError in Chrome
Uncaught ReferenceError in Chrome

ES6 introduce anche una terza parola chiave: const.

const è abbastanza simile a let, ma con una differenza fondamentale:

Consideriamo il seguente esempio:

const MAX_VALUE = 1000;
MAX_VALUE = 2000;

Il codice sopra riportato genererebbe il seguente TypeError:

Uncaught TypeError: Assignment to constant variable in Google Chrome
Uncaught TypeError: Assignment to constant variable in Google Chrome

Inoltre:

Dichiarare una const senza dargli un valore provocherebbe il seguente SyntaxError (vedi anche ES6 In Depth: let e const):

Uncaught SyntaxError: Missing initializer in const declaration in Chrome
Uncaught SyntaxError: Missing initializer in const declaration in Chrome

Tuttavia, se una costante è un array o un oggetto, è possibile modificare le proprietà o gli elementi di quell’array o di quell’oggetto.

Ad esempio, si possono modificare, aggiungere e rimuovere elementi di un array:

// Declare a constant array
const cities = ["London", "New York", "Sydney"];

// Change an item
cities[0] = "Madrid";

// Add an item
cities.push("Paris");

// Remove an item
cities.pop();

console.log(cities);

// Array(3)
// 0: "Madrid"
// 1: "New York"
// 2: "Sydney"

Ma non si può riassegnare l’array:

const cities = ["London", "New York", "Sydney"];

cities = ["Athens", "Barcelona", "Naples"];

Il codice sopra riportato provocherebbe un TypeError.

Uncaught TypeError: Assignment to constant variable.
Uncaught TypeError: Assignment to constant variable in Chrome

È possibile aggiungere, riassegnare e rimuovere proprietà e metodi degli oggetti:

// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language',
	content: 'JavaScript is a scripting language that enables you to create dynamically updating content.'
};

// add a new property
post.slug = "javascript-is-awesome";

// Reassign property
post.id = 5;

// Delete a property
delete post.excerpt;

console.log(post);

// {id: 5, name: 'JavaScript is awesome', content: 'JavaScript is a scripting language that enables you to create dynamically updating content.', slug: 'javascript-is-awesome'}

Ma non è possibile riassegnare l’oggetto stesso. Il seguente codice genererebbe un Uncaught TypeError:

// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language'
};

post = {
	id: 1,
	name: 'React is powerful',
	excerpt: 'React lets you build user interfaces'
};

Object.freeze()

Siamo d’accordo sul fatto che l’utilizzo di const non garantisce una forte immutabilità (soprattutto quando si lavora con oggetti e array). Quindi, come implementare il pattern di immutabilità nelle applicazioni React?

Innanzitutto, quando si vuole evitare che gli elementi di un array o le proprietà di un oggetto vengano modificati, è possibile utilizzare il metodo statico Object.freeze().

Il congelamento di un oggetto impedisce le estensioni e rende le proprietà esistenti non scrivibili e non configurabili. Un oggetto congelato non può più essere modificato: non si possono aggiungere nuove proprietà, non si possono rimuovere quelle esistenti, non si possono modificare la loro enumerabilità, configurabilità, scrivibilità o valore e non si può riassegnare il prototipo dell’oggetto. freeze() restituisce lo stesso oggetto che è stato passato.

Qualsiasi tentativo di aggiungere, modificare o rimuovere una proprietà fallirà, sia silenziosamente che lanciando un TypeError, più comunemente in modalità strict.

Object.freeze() può essere utilizzato in questo modo:

'use strict'
// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language'
};
// Freeze the object
Object.freeze(post);

Se ora provate ad aggiungere una proprietà, riceverete un Uncaught TypeError:

// Add a new property
post.slug = "javascript-is-awesome"; // Uncaught TypeError
Uncaught TypeError: can't define property
Uncaught TypeError: can’t define property “slug”: Object is not extensible in Firefox

Quando si cerca di riassegnare una proprietà, si ottiene un altro tipo di TypeError:

// Reassign property
post.id = 5; // Uncaught TypeError
Riassegnando una proprietà di sola lettura si genera un Uncaught TypeError
Riassegnando una proprietà di sola lettura si genera un Uncaught TypeError
Uncaught TypeError: Cannot assign to read only property in Chrome
Uncaught TypeError: Cannot assign to read only property ‘id’ of object ‘#<Object>’ in Google Chrome

È anche possibile provare a cancellare una proprietà. Il risultato sarà un altro TypeError:

// Delete a property
delete post.excerpt; // Uncaught TypeError
Uncaught TypeError: property
Uncaught TypeError: property “excerpt” is non-configurable and can’t be deleted in Firefox

Template Literals

Quando è necessario combinare delle stringhe con l’output di espressioni in JavaScript, di solito si usa l’operatore di addizione +. Tuttavia, è possibile anche utilizzare una funzione JavaScript che permette di includere espressioni all’interno di stringhe senza utilizzare l’operatore di addizione: i template literals.

I template literals sono un tipo speciale di stringhe delimitate da caratteri backtick (`).

Nei template literals è possibile includere dei placeholder, che sono espressioni incorporate delimitate da un carattere dollaro e incluse in parentesi graffe.

Ecco un esempio:

const align = 'left';
console.log(`This string is ${ align }-aligned`);

Le stringhe e i placeholder vengono passati a una funzione predefinita che esegue un’interpolazione delle stringhe per sostituire i segnaposto e concatenare le parti in un’unica stringa. È anche possibile sostituire la funzione predefinita con una funzione personalizzata.

I template literals possono essere utilizzati per:

Stringhe multilinea: i caratteri newline fanno parte del template literals.

console.log(`Twinkle, twinkle, little bat!
How I wonder what you’re at!`);

Interpolazione di stringhe: Senza i template literals, si può utilizzare solo l’operatore di addizione per combinare l’output delle espressioni con le stringhe. Ecco un esempio:

const a = 3;
const b = 7;
console.log("The result of " + a + " + " + b + " is " + (a + b));

È un po’ complicato, vero? Ma è possibile scrivere questo codice in modo più leggibile e mantenibile utilizzando i Template Literals:

const a = 3;
const b = 7;
console.log(`The result of ${ a } + ${ b } is ${ a + b }`);

Ma si tenga presente che c’è una differenza tra le due sintassi:

I template literals si prestano a diversi impieghi. Nell’esempio che segue, utilizziamo un operatore ternario per assegnare un valore all’attributo class.

const page = 'archive';
console.log(`class=${ page === 'archive' ? 'archive' : 'single' }`);

Di seguito, eseguiamo un semplice calcolo:

const price = 100;
const VAT = 0.22;

console.log(`Total price: ${ (price * (1 + VAT)).toFixed(2) }`);

È anche possibile annidare i template literal includendoli all’interno di un placeholder ${expression} (ma si consiglia di usare i template annidati con cautela perché le strutture di stringhe complesse possono essere difficili da leggere e mantenere).

Tagged template: Come abbiamo detto in precedenza, è anche possibile definire una funzione personalizzata per eseguire la concatenazione di stringhe. Questo tipo di template literal è chiamato tagged template.

I tag permettono di eseguire il parsing dei template literal con una funzione. Il primo argomento di una funzione tag contiene un array di valori stringa. Gli altri argomenti sono relativi alle espressioni.

I tag permettono di eseguire il parsing dei template literal con una funzione personalizzata. Il primo argomento di questa funzione è un array di stringhe incluse nel template literal, mentre gli altri argomenti sono le espressioni.

È possibile creare una funzione personalizzata per eseguire qualsiasi tipo di operazione sugli argomenti del template e restituire la stringa manipolata. Ecco un esempio molto semplice di tagged template:

const name = "Carlo";
const role = "student";
const organization = "North Pole University";
const age = 25;

function customFunc(strings, ...tags) {
	console.log(strings); // ['My name is ', ", I'm ", ', and I am ', ' at ', '', raw: Array(5)]
	console.log(tags); // ['Carlo', 25, 'student', 'North Pole University']
	let string = '';
	for ( let i = 0; i < strings.length - 1; i++ ){
		console.log(i + "" + strings[i] + "" + tags[i]);
		string += strings[i] + tags[i];
	}
	return string.toUpperCase();
}

const output = customFunc`My name is ${name}, I'm ${age}, and I am ${role} at ${organization}`;
console.log(output);

Il codice qui sopra stampa gli elementi dell’array strings e tags e rende maiuscoli i caratteri della stringa prima di stampare l’output nella console del browser.

Arrow Function

Le arrow function sono un’alternativa alle funzioni anonime (funzioni senza nome) in JavaScript, ma con alcune differenze e limitazioni.

Le seguenti dichiarazioni sono tutti esempi validi di arrow function:

// Arrow function without parameters
const myFunction = () => expression;

// Arrow function with one parameter
const myFunction = param => expression;

// Arrow function with one parameter
const myFunction = (param) => expression;

// Arrow function with more parameters
const myFunction = (param1, param2) => expression;

// Arrow function without parameters
const myFunction = () => {
	statements
}

// Arrow function with one parameter
const myFunction = param => {
	statements
}

// Arrow function with more parameters
const myFunction = (param1, param2) => {
	statements
}

È possibile omettere le parentesi tonde se si passa un solo parametro alla funzione. Se si passano due o più parametri, è necessario racchiuderli tra parentesi. Ecco un esempio:

const render = ( id, title, category ) => `${id}: ${title} - ${category}`;
console.log( render ( 5, 'Hello World!', "JavaScript" ) );

Di default, le arrow function a una riga restituiscono un valore. Se si usa la sintassi a più righe, è necessario restituire manualmente un valore:

const render = ( id, title, category ) => {
	console.log( `Post title: ${ title }` );
	return `${ id }: ${ title } - ${ category }`;
}
console.log( `Post details: ${ render ( 5, 'Hello World!', "JavaScript" ) }` );

Una differenza fondamentale da tenere a mente tra le normali funzioni e le arrow function è che le arrow function non hanno un proprio legame alla parola chiave this. Se si prova a utilizzare this in una arrow function, questa uscirà dallo scopo della funzione.

Per una descrizione più approfondita delle arrow function ed esempi di utilizzo, mdn web docs.

Le Classi

Le classi JavaScript sono un tipo speciale di funzione per creare oggetti che utilizzano il meccanismo dell’ereditarietà prototipica.

Secondo l’mdn web docs,

Quando si parla di ereditarietà, JavaScript ha un solo costrutto: gli oggetti. Ogni oggetto ha una proprietà privata che contiene un link a un altro oggetto chiamato prototipo. Questo oggetto prototipo ha un suo prototipo e così via fino a quando non si raggiunge un oggetto con null come prototipo.

Come per le funzioni, esistono due modi per definire una classe:

  • Un’espressione di classe
  • Una dichiarazione di classe

È possibile utilizzare la parola chiave class per definire una classe all’interno di un’espressione, come mostrato nel seguente esempio:

const Circle = class {
	constructor(radius) {
		this.radius = Number(radius);
	}
	area() {
		return Math.PI * Math.pow(this.radius, 2);
	}
	circumference() {
		return Math.PI * this.radius * 2;
	}
}
console.log('Circumference: ' + new Circle(10).circumference()); // 62.83185307179586
console.log('Area: ' + new Circle(10).area()); // 314.1592653589793

Una classe ha un corpo, cioè il codice incluso nelle parentesi graffe. Qui si definiscono i costruttori e i metodi, che sono anche chiamati membri della classe. Il corpo della classe viene eseguito in modalità strict anche senza utilizzare la direttiva 'strict mode'.

Il metodo constructor è utilizzato per creare e inizializzare un oggetto creato con una classe e viene eseguito automaticamente quando la classe viene istanziata. Se nella classe non si definisce un metodo costruttore, JavaScript utilizzerà automaticamente un costruttore predefinito.

Una classe può essere estesa utilizzando la parola chiave extends.

class Book {
	constructor(title, author) {
		this.booktitle = title;
		this.authorname = author;
	}
	present() {
		return this.booktitle + ' is a great book from ' + this.authorname;
	}
}

class BookDetails extends Book {
	constructor(title, author, cat) {
		super(title, author);
		this.category = cat;
	}
	show() {
		return this.present() + ', it is a ' + this.category + ' book';
	}
}

const bookInfo = new BookDetails("The Fellowship of the Ring", "J. R. R. Tolkien", "Fantasy");
console.log(bookInfo.show());

Un costruttore può utilizzare la parola chiave super per invocare il costruttore genitore. Se al metodo super() viene passato un argomento, questo argomento sarà disponibile anche nella classe del costruttore genitore.

Per un approfondimento sulle classi JavaScript e diversi esempi di utilizzo, si veda mdn web docs.

Le classi sono spesso utilizzate per creare componenti React. Di solito non si creano le proprie classi ma si estendono le classi React integrate.

Tutte le classi di React hanno un metodo render() che restituisce un elemento React:

class Animal extends React.Component {
	render() {
		return <h2>Hey, I am a {this.props.name}!</h2>;
	}
}

Nell’esempio precedente, Animal è un componente della classe. Si tenga presente che

  • Il nome del componente deve iniziare con una lettera maiuscola
  • Il componente deve includere l’espressione extends React.Component. Questo dà accesso ai metodi del componente React.Component.
  • Il metodo render() restituisce l’HTML ed è obbligatorio.

Una volta creato il componente di classe, è possibile eseguire il rendering dell’HTML sulla pagina:

const root = ReactDOM.createRoot(document.getElementById('root'));
const element = <Animal name="Rabbit" />;
root.render(element);

L’immagine qui sotto mostra il risultato sulla pagina (in azione su CodePen).

Un semplice componente di classe React
Un semplice componente di classe React

Si noti, tuttavia, che l’utilizzo di componenti di classe in React non è raccomandato ed è preferibile definire i componenti come funzioni.

La Parola Chiave “this”

In JavaScript, la parola chiave this è un placeholder generico solitamente utilizzato all’interno di oggetti, classi e funzioni e si riferisce a elementi diversi a seconda del contesto o dello scopo.

this può essere utilizzata nell’ambito globale. Digitando this nella console del browser, si ottiene:

Window {window: Window, self: Window, document: document, name: '', location: Location, ...}

È possibile accedere a qualsiasi metodo e proprietà dell’oggetto Window. Quindi, se si esegue this.location nella console del browser, si ottiene il seguente risultato:

Location {ancestorOrigins: DOMStringList, href: 'https://kinsta.com/', origin: 'https://kinsta.com', protocol: 'https:', host: 'kinsta.com', ...}

Se si utilizza this in un oggetto, questo si riferisce all’oggetto stesso. In questo modo, è possibile fare riferimento ai valori di un oggetto nei metodi dell’oggetto stesso:

const post = { 
	id: 5,
	getSlug: function(){
		return `post-${this.id}`;
	},
	title: 'Awesome post', 
	category: 'JavaScript' 
};
console.log( post.getSlug );

Ora proviamo a utilizzare this in una funzione:

const useThis = function () {
	return this;
}
console.log( useThis() );

Se non si è in modalità strict, si ottiene:

Window {window: Window, self: Window, document: document, name: '', location: Location, ...}

Ma se si invoca la modalità strict, si ottiene un risultato diverso:

const doSomething = function () {
	'use strict';
	return this;
}
console.log( doSomething() );

In questo caso, la funzione restituisce undefined. Questo perché this in una funzione si riferisce al suo valore esplicito.

Come impostare esplicitamente this in una funzione?

Innanzitutto, è possibile assegnare manualmente proprietà e metodi alla funzione:

function doSomething( post ) {
	this.id = post.id;
	this.title = post.title;
	console.log( `${this.id} - ${this.title}` );
}
new doSomething( { id: 5, title: 'Awesome post' } );

Ma è possibile anche utilizzare i metodi call, apply e bind, così come le arrow function.

const doSomething = function() {
	console.log( `${this.id} - ${this.title}` );
}
doSomething.call( { id: 5, title: 'Awesome post' } );

Il metodo call() può essere utilizzato su qualsiasi funzione e fa esattamente quello che dice: chiama la funzione.

Inoltre, call() accetta qualsiasi altro parametro definito nella funzione:

const doSomething = function( cat ) {
	console.log( `${this.id} - ${this.title} - Category: ${cat}` );
}
doSomething.call( { id: 5, title: 'Awesome post' }, 'JavaScript' );
const doSomething = function( cat1, cat2 ) {
	console.log( `${this.id} - ${this.title} - Categories: ${cat1}, ${cat2}` );
}
doSomething.apply( { id: 5, title: 'Awesome post' }, ['JavaScript', 'React'] );
const post = { id: 5, title: 'Awesome post', category: 'JavaScript' };
const doSomething = function() {
	return `${this.id} - ${this.title} - ${this.category}`;
}
const bindRender = doSomething.bind( post );
console.log( bindRender() );

Un’alternativa alle opzioni descritte in precedenza sono le arrow function.

Le espressioni delle arrow function dovrebbero essere utilizzate solo per le funzioni senza metodo perché non hanno un proprio this.

Questo rende le arrow function particolarmente utili con gli event handler.

Infatti, “quando il codice viene richiamato da un attributo di un event handler in linea, il suo this viene impostato sull’elemento DOM su cui è posizionato il listener” (si legga mdn web docs).

Ma le cose cambiano con le arrow function perché…

… le arrow function stabiliscono this in base allo scopo in cui l’arrow function è definita e il valore this non cambia in base al modo in cui la funzione viene invocata.

Legare ‘this’ agli Event Handler in React

In React ci sono diversi modi per assicurarsi che l’event handler non perda il suo contesto:

1. Utilizzando bind() all’interno del metodo di rendering:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage(){
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={ this.showMessage.bind( this ) }>Show message from state!</button> );
	}
}
export default MyComponent;

2. Legando lo scopo all’event handler nel costruttore:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	constructor(props) {
		super(props);
		this.showMessage = this.showMessage.bind( this );
	}

	showMessage(){
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={ this.showMessage }>Show message from state!</button> );
	}
}
export default MyComponent;

3. Definendo il gestore di eventi utilizzando le arrow function:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage = () => {
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={this.showMessage}>Show message from state!</button> );
	}
}
export default MyComponent;

4. Utilizzando le arrow function nel metodo di rendering:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage() {
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={()=>{this.showMessage()}}>Show message from state!</button> );
	}
}
export default MyComponent;

Qualunque sia il metodo scelto, quando si fa clic sul pulsante, la console del browser mostra il seguente output:

This refers to:  MyComponent {props: {…}, context: {…}, refs: {…}, updater: {…}, state: {…}, …}
The message is:  Hello World!

Operatore Ternario

L’operatore condizionale (o operatore ternario) permette di scrivere semplici espressioni condizionali in JavaScript. Richiede tre operandi:

  • una condizione seguita da un punto interrogativo (?),
  • un’espressione da eseguire se la condizione è vera seguita da duepunti (:),
  • una seconda espressione da eseguire se la condizione è falsa.
const drink = personAge >= 18 ? "Wine" : "Juice";

È anche possibile concatenare più espressioni:

const drink = personAge >= 18 ? "Wine" : personAge >= 6 ? "Juice" : "Milk";

Si faccia attenzione, però, perché il concatenamento di più espressioni può portare a un codice disordinato e difficile da mantenere.

L’operatore ternario è particolarmente utile in React, soprattutto nel codice JSX, che accetta solo espressioni tra parentesi graffe.

Ad esempio, è possibile usare l’operatore ternario per impostare il valore di un attributo in base a una condizione specifica:

render(){			
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/...',
		description: 'Content Writer',
		theme: 'light'
	}

	return (
		<div
			className='card' 
			style={
				person.theme === 'dark' ? 
				{ background: 'black', color: 'white' } : 
				{ background: 'white', color: 'black'} 
			}>
			<img
				src={person.avatar}
				alt={person.name}
				style={ { width: '100%' } }
			/>
			<div style={ { padding: '2px 16px' } }>
				<h3>{person.name}</h3>
				<p>{person.description}.</p>
			</div>
		</div>
	);
}

Nel codice qui sopra, controlliamo la condizione person.theme === 'dark' per impostare il valore dell’attributo style della div.

Valutazione Short Circuit

L’operatore logico AND (&&) valuta gli operandi da sinistra a destra e restituisce true se e solo se tutti gli operandi sono true.

L’operatore logico AND è un operatore Short Circuit. Ogni operando viene convertito in un booleano e, se il risultato della conversione è false, l’operatore AND si ferma e restituisce il valore originale dell’operando falso. Se tutti i valori sono true, restituisce il valore originale dell’ultimo operando.

La valutazione del cortocircuito è una funzione di JavaScript utilizzata spesso in React, in quanto consente di restituire blocchi di codice in base a condizioni specifiche. Ecco un esempio:

{
	displayExcerpt &&
	post.excerpt.rendered && (
		<p>
			<RawHTML>
				{ post.excerpt.rendered }
			</RawHTML>
		</p>
	)
}

Nel codice qui sopra, se displayExcerpt AND post.excerpt.rendered sono true, React restituisce il blocco finale di JSX.

Ricapitolando, “se la condizione è true, l’elemento subito dopo && apparirà nell’output. Se è false, React lo ignorerà e lo salterà”.

Sintassi Spread

In JavaScript, la sintassi spread permette di espandere un elemento iterabile, come un array o un oggetto, in argomenti di funzione, letterali di array o letterali di oggetto.

Nell’esempio che seguente, scompattiamo un array in una chiamata di funzione:

function doSomething( x, y, z ){
	return `First: ${x} - Second: ${y} - Third: ${z} - Sum: ${x+y+z}`;
}
const numbers = [3, 4, 7];
console.log( doSomething( ...numbers ) );

È possibile utilizzare la sintassi spread per duplicare un array (anche multidimensionale) o per concatenare array. Negli esempi seguenti, concateniamo due array in due modi diversi:

const firstArray = [1, 2, 3];
const secondArray = [4, 5, 6];
firstArray.push( ...secondArray );
console.log( firstArray );

In alternativa:

let firstArray = [1, 2, 3];
const secondArray = [4, 5, 6];
firstArray = [ ...firstArray, ...secondArray];
console.log( firstArray );

È possibile utilizzare la sintassi spread anche per clonare o unire due oggetti:

const firstObj = { id: '1', title: 'JS is awesome' };
const secondObj = { cat: 'React', description: 'React is easy' };

// clone object
const thirdObj = { ...firstObj };

// merge objects
const fourthObj = { ...firstObj, ...secondObj }

console.log( { ...thirdObj } );
console.log( { ...fourthObj } );

Destrutturazione

Un’altra struttura sintattica spesso utilizzata in React è la sintassi di destrutturazione.

Nell’esempio che segue, scompattiamo i valori da un array:

const user = ['Carlo', 'Content writer', 'Kinsta'];
const [name, description, company] = user;
console.log( `${name} is ${description} at ${company}` );

Ed ecco un semplice esempio di destrutturazione con un oggetto:

const user = {
	name: 'Carlo',
	description: 'Content writer',
	company: 'Kinsta'
}
const { name, description, company } = user;
console.log( `${name} is ${description} at ${company}` );

Ma possiamo fare ancora di più. Nel prossimo esempio, scompattiamo alcune proprietà di un oggetto e assegniamo le proprietà rimanenti a un altro oggetto utilizzando la spread syntax:

const user = {
	name: 'Carlo',
	family: 'Daniele',
	description: 'Content writer',
	company: 'Kinsta',
	power: 'swimming'
}
const { name, description, company, ...rest } = user;
console.log( rest ); // {family: 'Daniele', power: 'swimming'}

È anche possibile assegnare valori a un array:

const user = [];
const object = { name: 'Carlo', company: 'Kinsta' };
( { name: user[0], company: user[1] } = object );
console.log( user ); // (2) ['Carlo', 'Kinsta']

Si noti che le parentesi intorno alla dichiarazione di assegnazione sono necessarie quando si utilizza una destrutturazione letterale dell’oggetto senza dichiarazione.

Per un’analisi più approfondita della destrutturazione, con diversi esempi di utilizzo, si veda mdn web docs.

filter(), map() e reduce()

JavaScript offre diversi metodi utili spesso utilizzati in React.

filter()

Nell’esempio che segue, applichiamo il filtro all’array numbers per ottenere un array i cui elementi sono numeri superiori a 5:

const numbers = [2, 6, 8, 2, 5, 9, 23];
const result = numbers.filter( number => number > 5);
console.log(result); // (4) [6, 8, 9, 23]

In quest’altro esempio, otteniamo un array di post con la parola “JavaScript” inclusa nel titolo:

const posts = [
	{id: 0, title: 'JavaScript is awesome', content: 'your content'},
	{id: 1, title: 'WordPress is easy', content: 'your content'},
	{id: 2, title: 'React is cool', content: 'your content'},
	{id: 3, title: 'With JavaScript to the moon', content: 'your content'},
];

const jsPosts = posts.filter( post => post.title.includes( 'JavaScript' ) );

console.log( jsPosts );
Un array di post il cui titolo contiene la parola 'JavaScript'
Un array di post il cui titolo contiene la parola “JavaScript”

map()

const numbers = [2, 6, 8, 2, 5, 9, 23];
const result = numbers.map( number => number * 5 );
console.log(result); // (7) [10, 30, 40, 10, 25, 45, 115]

In un componente React, troverete spesso il metodo map() utilizzato per costruire elenchi. Nell’esempio qui sotto, mappiamo l’oggetto posts di WordPress per costruire un elenco di post:

<ul>
	{ posts && posts.map( ( post ) => {
		return (
			<li key={ post.id }>
				<h5>
					<a href={ post.link }>
						{ 
							post.title.rendered ? 
							post.title.rendered :
							__( 'Default title', 'author-plugin' )
						}
					</a>
				</h5>
			</li>
		)
	})}
</ul>

reduce()

reduce() accetta due parametri:

  • Una funzione di callback da eseguire per ogni elemento dell’array. Restituisce un valore che diventa il valore del parametro accumulatore alla chiamata successiva. All’ultima chiamata, la funzione restituisce il valore che sarà il valore di ritorno di reduce().
  • Un valore iniziale che è il primo valore dell’accumulator passato alla funzione di callback.

La funzione di callback richiede alcuni parametri:

  • Un accumulator: Il valore restituito dalla precedente chiamata alla funzione di callback. Alla prima chiamata, viene impostato su un valore iniziale, se specificato. Altrimenti, assume il valore del primo elemento dell’array.
  • Il valore dell’elemento corrente: Il valore viene impostato sul primo elemento dell’array (array[0]) se è stato impostato un valore iniziale, altrimenti assume il valore del secondo elemento (array[1]).
  • L’indice corrente è la posizione dell’indice dell’elemento corrente.

Un esempio renderà tutto più chiaro.

const numbers = [1, 2, 3, 4, 5];
const initialValue = 0;
const sumElements = numbers.reduce(
	( accumulator, currentValue ) => accumulator + currentValue,
	initialValue
);
console.log( numbers ); // (5) [1, 2, 3, 4, 5]
console.log( sumElements ); // 15

Scopriamo nel dettaglio cosa succede a ogni iterazione. Torniamo all’esempio precedente e modifichiamo initialValue:

const numbers = [1, 2, 3, 4, 5];
const initialValue = 5;
const sumElements = numbers.reduce(
	( accumulator, currentValue, index ) => {
		console.log('Accumulator: ' + accumulator + ' - currentValue: ' + currentValue + ' - index: ' + index);
		return accumulator + currentValue;
	},
	initialValue
);
console.log( sumElements );

L’immagine seguente mostra l’output nella console del browser:

Uso di reduce() con valore iniziale impostato a 5
Uso di reduce() con valore iniziale impostato a 5

Ora vediamo cosa succede senza il parametro initialValue:

const numbers = [1, 2, 3, 4, 5];
const sumElements = numbers.reduce(
	( accumulator, currentValue, index ) => {
		console.log( 'Accumulator: ' + accumulator + ' - currentValue: ' + currentValue + ' - index: ' + index );
		return accumulator + currentValue;
	}
);
console.log( sumElements );
Uso di reduce() senza valore iniziale
Uso di reduce() senza valore iniziale

Altri esempi e casi d’uso sono disponibili nel sito web mdn docs.

Export e Import

A partire da ECMAScript 2015 (ES6), è possibile esportare valori da un modulo JavaScript e importarli in un altro script. Le importazioni e le esportazioni sono molto utilizzate nelle applicazioni React e quindi è importante conoscerne bene il funzionamento.

Il codice che segue crea un componente funzionale. La prima riga importa la libreria React:

import React from 'react';

function MyComponent() {
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22?size=original',
		description: 'Content Writer',
		theme: 'dark'
	}
 
	return (
		<div
			className = 'card'
			style = {
				person.theme === 'dark' ?
				{ background: 'black', color: 'white' } :
				{ background: 'white', color: 'black'}
			}>
			<img
				src = { person.avatar }
				alt = { person.name }
				style = { { width: '100%' } }
			/>
			<div
				style = { { padding: '2px 16px' } }
			>
				<h3>{ person.name }</h3>
				<p>{ person.description }.</p>
			</div>
		</div>
	);
}
export default MyComponent;

Abbiamo usato la parola chiave import seguita dal nome che vogliamo assegnare a ciò che stiamo importando, seguito dal nome del pacchetto che vogliamo installare come viene indicato nel file package.json.

Si noti che nella funzione MyComponent() qui sopra, abbiamo utilizzato alcune delle caratteristiche di JavaScript descritte nelle sezioni precedenti. Abbiamo incluso i valori delle proprietà tra parentesi graffe e abbiamo assegnato il valore della proprietà style utilizzando la sintassi dell’operatore condizionale.

Lo script termina con l’esportazione del nostro componente personalizzato.

Ora che sappiamo qualcosa in più sulle importazioni e sulle esportazioni, diamo un’occhiata più da vicino al loro funzionamento.

Export

Ogni modulo React può avere due diversi tipi di esportazione: named export e default export.

Ad esempio, è possibile esportare più funzioni contemporaneamente con un’unica dichiarazione export:

export { MyComponent, MyVariable };

È anche possibile esportare singole funzioni (function, class, const, let):

export function MyComponent() { ... };
export let myVariable = x + y;

Ma è possibile avere una sola esportazione predefinita:

export default MyComponent;

Infine, è possibile utilizzare l’esportazione predefinita per singole funzionalità:

export default function() { ... }
export default class { ... }

Import

Una volta che il componente è stato esportato, può essere importato in un altro file, ad esempio un file index.js, insieme ad altri moduli:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import MyComponent from './MyComponent';

const root = ReactDOM.createRoot( document.getElementById( 'root' ) );
root.render(
	<React.StrictMode>
		<MyComponent />
	</React.StrictMode>
);

Nel codice qui sopra, abbiamo utilizzato la dichiarazione di importazione in diversi modi.

Nelle prime due righe abbiamo assegnato un nome alle risorse importate, nella terza riga non abbiamo assegnato un nome ma abbiamo semplicemente importato il file ./index.css. L’ultima dichiarazione import importa il file ./MyComponent e assegna un nome.

Scopriamo le differenze tra queste importazioni.

In totale, esistono quattro tipi di import:

Named import

import { MyFunction, MyVariable } from "./my-module";

Default import

import MyComponent from "./MyComponent";

Namespace import

import * as name from "my-module";

Side effect import

import "module-name";

Una volta aggiunti alcuni stili nel vostro index.css, la nostra scheda dovrebbe apparire come nell’immagine qui sotto, dove è possibile vedere anche il codice HTML corrispondente:

Un semplice componente React
Un semplice componente React

Si noti che le dichiarazioni import possono essere utilizzate solo nei moduli di livello superiore (non all’interno di funzioni, classi, ecc.).

Per una panoramica più completa delle dichiarazioni import ed export, si leggano le seguenti risorse:

Riepilogo

React è una delle librerie JavaScript più popolari ed è una delle competenze più richieste nel mondo dello sviluppo web.

Con React è possibile creare applicazioni web dinamiche e interfacce avanzate. E grazie ai suoi componenti riutilizzabili, può essere davvero semplice creare applicazioni grandi, dinamiche e interattive.

Ma React è una libreria JavaScript, e una buona conoscenza delle principali caratteristiche di JavaScript è essenziale per iniziare un percorso di carriera con React. Ecco perché abbiamo raccolto in un unico posto alcune delle funzionalità di JavaScript più spesso utilizzate in React. La padronanza di queste funzionalità, può dare una marcia in più nel percorso di apprendimento di React.

E quando si tratta di sviluppo web, passare da JS/React a WordPress richiede davvero un piccolissimo sforzo.

Ora tocca a voi: quali sono le funzionalità di JavaScript che ritenete più utili con React? Abbiamo tralasciato qualcosa di importante che avreste voluto vedere nel nostro elenco? Condividete i vostri pensieri con noi nei commenti qui sotto.

Carlo Daniele Kinsta

Carlo è cultore appassionato di webdesign e front-end development. Gioca con WordPress da oltre 20 anni, anche in collaborazione con università ed enti educativi italiani ed europei. Su WordPress ha scritto centinaia di articoli e guide, pubblicati sia in siti web italiani e internazionali, che su riviste a stampa. Lo trovate su LinkedIn.