Skip to content

Appunti React

Published:  at 10:00 AM

Queste sono le note dell’ultima lezione del corso, quella su React. Nella lezione su Angular abbiamo ricostruito il menu di Awesome Pizza con un framework; oggi costruiamo la stessa identica applicazione con React. Stesso risultato, filosofia opposta, ed è proprio il confronto la parte interessante.

Dove Angular è un framework completo, con la sua CLI, la dependency injection e una struttura precisa per ogni cosa, React è una libreria con un’idea sola: l’interfaccia è una funzione dello stato. Voi descrivete cosa mostrare dato lo stato attuale, React aggiorna il DOM quando lo stato cambia. Tutto il resto, e intendo davvero tutto il resto, dal routing alla gestione delle chiamate HTTP, sono scelte vostre.

Anche qui usiamo TypeScript: React non lo impone come Angular, ma noi ormai sappiamo perché conviene.

Il primo progetto

Nel 2023 un progetto React si crea con Vite:

npm create vite@latest awesome-pizza -- --template react-ts
cd awesome-pizza
npm install
npm run dev

Una nota per quando cercherete tutorial in giro: quelli più datati usano create-react-app, lo strumento storico ormai in pensione. Se un tutorial inizia con npx create-react-app, controllate la data di pubblicazione e tenete conto che il resto sarà altrettanto vecchio.

Il server di sviluppo parte su http://localhost:5173. Anche qui la cartella che ci interessa è src/.

JSX

In Angular la classe e il template stanno in file separati. React fa la scelta opposta: un componente è una funzione JavaScript che ritorna il markup, e markup e logica convivono nello stesso file. La sintassi si chiama JSX.

export function App() {
  const nome = "Awesome Pizza";

  return (
    <main>
      <h1>{nome}</h1>
      <p>La migliore pizza di Paperopoli.</p>
    </main>
  );
}

Le graffe {} aprono una finestra su JavaScript: dentro ci va una qualsiasi espressione, che viene valutata e inserita nel markup. È il parente stretto dei template literals della lezione su JavaScript.

JSX somiglia all’HTML ma non lo è, e le differenze che incontrerete subito sono tre:

DON’T: trattare JSX come se fosse HTML

L’errore concettuale da evitare è pensare di “scrivere HTML dentro JavaScript”. State scrivendo JavaScript travestito: ogni tag diventa una chiamata di funzione, ogni attributo una proprietà. È per questo che class non era disponibile, ed è per questo che dentro le graffe ci va un’espressione e non un’istruzione: niente if, niente for. Come si fa allora un ciclo? Con gli strumenti che JavaScript già ci dà, e li conoscete.

Props

I componenti ricevono dati tramite le props, che in TypeScript sono semplicemente i parametri tipizzati della funzione. Riprendiamo il tipo Pizza di sempre, in un file types.ts:

export type Pizza = {
  nome: string;
  prezzo: number;
  ingredienti: string[];
  sconto?: number;
};

E costruiamo la card:

import type { Pizza } from "./types";

type PizzaCardProps = {
  pizza: Pizza;
  onOrdina: () => void;
};

export function PizzaCard({ pizza, onOrdina }: PizzaCardProps) {
  return (
    <article className="card">
      <h3>{pizza.nome}</h3>
      <p>{pizza.ingredienti.join(", ")}</p>
      <p className="prezzo">{pizza.prezzo}</p>
      <button onClick={onOrdina}>Ordina</button>
    </article>
  );
}

La prop onOrdina è una funzione: il genitore la passa, la card la chiama quando l’utente clicca. È lo stesso schema visto in Angular con @Input e @Output, solo che qui non serve grammatica dedicata, perché le funzioni passate come parametri le abbiamo già incontrate nella lezione su JavaScript parlando di first-class functions. I dati scendono via props, gli eventi risalgono via callback.

Il menu con map

In Angular il menu si ripeteva con *ngFor. React non ha una sintassi per i cicli, e usa il map della lezione su JavaScript, letteralmente: un array di pizze diventa un array di elementi JSX, e React lo renderizza.

import { PizzaCard } from "./PizzaCard";
import type { Pizza } from "./types";

const pizze: Pizza[] = [
  {
    nome: "Margherita",
    prezzo: 9,
    ingredienti: ["Farina", "Pomodoro", "Mozzarella"],
  },
  {
    nome: "Marinara",
    prezzo: 5,
    ingredienti: ["Farina", "Pomodoro", "Aglio", "Origano"],
  },
  {
    nome: "Contadina",
    prezzo: 19,
    ingredienti: ["Farina", "Melanzane", "Peperoni", "Mozzarella"],
  },
];

export function App() {
  return (
    <main className="menu">
      {pizze.map(pizza => (
        <PizzaCard key={pizza.nome} pizza={pizza} onOrdina={() => {}} />
      ))}
    </main>
  );
}

L’attributo key serve a React per riconoscere gli elementi della lista tra un render e l’altro, e va valorizzato con qualcosa che identifichi l’elemento in modo stabile, qui il nome della pizza. Se lo dimenticate, React ve lo dice in console.

Conditional rendering

Anche per le condizioni si usano gli strumenti di JavaScript: l’operatore ternario della lezione sul linguaggio. Aggiungiamo alla card la riga dell’offerta:

<article className="card">
  {/* ...il resto della card... */}
  {pizza.sconto ? <p>In offerta: -{pizza.sconto}</p> : null}
</article>

In giro vedrete spesso la variante con &&:

<article className="card">
  {/* ...il resto della card... */}
  {pizza.sconto && <p>In offerta: -{pizza.sconto}</p>}
</article>

Funziona, ma nasconde una trappola che potete già spiegare da soli: ricordate i valori falsy? Se sconto vale 0, l’espressione vale 0, e React un numero lo renderizza. Vi ritrovereste uno zero solitario stampato nella card. Con le stringhe e gli oggetti il && va benissimo; con i numeri usate il ternario o un confronto esplicito.

State

Manca il carrello, e qui arriva il pezzo veramente nuovo: lo stato. In Angular ci bastava una proprietà della classe; in React lo stato si dichiara con useState:

import { useState } from "react";

export function App() {
  const [carrello, setCarrello] = useState(0);

  return (
    <>
      <header>
        <h1>Awesome Pizza</h1>
      </header>

      <main className="menu">
        {pizze.map(pizza => (
          <PizzaCard
            key={pizza.nome}
            pizza={pizza}
            onOrdina={() => setCarrello(carrello + 1)}
          />
        ))}
      </main>

      <footer>
        <p>Pizze nel carrello: {carrello}</p>
      </footer>
    </>
  );
}

useState restituisce una coppia, che destrutturiamo: il valore attuale e la funzione per sostituirlo. La regola fondamentale è che lo stato non si modifica mai direttamente: niente carrello = carrello + 1, sempre setCarrello(carrello + 1). È la chiamata alla funzione setter che avvisa React che qualcosa è cambiato e che la funzione componente va rieseguita per produrre la nuova interfaccia.

useState appartiene alla famiglia degli hook, le funzioni con cui i componenti accedono alle capacità di React. Ne esistono diversi, e il secondo della lista ci serve subito.

Caricare il menu dalle API

Come per Angular, il menu vero arriva dal backend. Recuperiamo la getJson scritta nella lezione su TypeScript, generics compresi:

async function getJson<T>(url: string): Promise<T> {
  const response = await fetch(url);
  return response.json();
}

Una chiamata HTTP è un’interazione con il mondo esterno, quello che React chiama side effect, e ha il suo hook dedicato, useEffect:

import { useEffect, useState } from "react";

export function App() {
  const [pizze, setPizze] = useState<Pizza[]>([]);
  const [caricamento, setCaricamento] = useState(true);

  useEffect(() => {
    getJson<Pizza[]>("https://api.awesomepizza.com/pizzas").then(dati => {
      setPizze(dati);
      setCaricamento(false);
    });
  }, []);

  if (caricamento) {
    return <p>Un attimo, carico il menu...</p>;
  }

  return <main className="menu">{/* il map di prima */}</main>;
}

Le cose da notare:

E quando questa chiamata verso il backend vero fallirà con l’errore rosso in console, voi ormai siete persone che hanno letto la lezione su CORS e sanno esattamente a chi dare la colpa.

Dove finisce il corso

Avete due versioni dello stesso menu: una in Angular, una in React. Apritele fianco a fianco e confrontatele pezzo per pezzo: il *ngFor contro il map, @Input/@Output contro le props, il servizio iniettato contro la funzione importata, l’Observable contro la Promise. I framework passano e si somigliano tutti più di quanto i rispettivi sostenitori siano disposti ad ammettere; quello che resta sono i fondamentali, ed è il motivo per cui questo corso ha passato sette lezioni sui fondamentali e due sui framework, e non il contrario.

Per approfondire