Nella lezione precedente hai installato Node.js, configurato VS Code e creato il primo progetto Vite + React. Il dev server gira, la pagina di default si aggiorna istantaneamente. Ora viene la parte interessante: capire come funziona davvero React e scrivere i primi componenti personalizzati per il progetto DevNotes.
In questa lezione affrontiamo i due concetti fondamentali di React: JSX e i componenti. Non sono complicati, ma è cruciale capirli bene — tutto il resto di React si costruisce sopra questi due mattoni.
Organizziamo il progetto DevNotes in una struttura professionale, sostituiamo il codice di default con i primi componenti reali: Header, NoteCard e NoteList. Impariamo a passare dati tra componenti con le props e capiamo la differenza tra export default e named export.
Cos’è JSX — e perché esiste
Quando apri src/App.jsx vedrai qualcosa di strano: codice JavaScript che contiene tag tipo HTML, senza virgolette, in mezzo a funzioni normali. Questo è JSX — JavaScript XML.
JSX non è HTML. Non lo capisce il browser. Non lo capisce neanche Node.js da solo. È una sintassi estesa di JavaScript che Vite (tramite il plugin @vitejs/plugin-react che usa Babel) trasforma in chiamate JavaScript standard prima di servire il file al browser.
Concretamente, questo JSX:
const elemento = ( <div className="card"> <h2>Titolo nota</h2> <p>Contenuto della nota</p> </div> )
viene trasformato da Vite in questo JavaScript puro:
const elemento = React.createElement( 'div', { className: 'card' }, React.createElement('h2', null, 'Titolo nota'), React.createElement('p', null, 'Contenuto della nota') )
Il secondo è equivalente al primo, ma è illeggibile per un essere umano. JSX esiste proprio per questo: rende il codice dell’interfaccia leggibile e manutenibile, senza sacrificare la potenza di JavaScript.
JSX è un’estensione sintattica di JavaScript che permette di scrivere strutture simili all’HTML direttamente nel codice JS. Non è un linguaggio separato: viene compilato in chiamate React.createElement() da strumenti come Babel o esbuild (usato internamente da Vite). Il risultato è un React Element: un oggetto JavaScript che descrive cosa rendere nel DOM.
Le differenze tra JSX e HTML
JSX assomiglia a HTML ma ha regole diverse. Conoscerle evita ore di debug su errori apparentemente misteriosi.
| Regola | HTML | JSX | Motivo |
|---|---|---|---|
| Attributo classe | class="card" | className="card" | class è una parola riservata in JavaScript |
| Attributo for (label) | for="email" | htmlFor="email" | for è una parola riservata in JavaScript |
| Tag auto-chiudenti | <input> <img> <br> | <input /> <img /> <br /> | In JSX tutti i tag devono essere chiusi esplicitamente |
| Attributi camelCase | onclick tabindex | onClick tabIndex | JSX usa convenzioni JavaScript, non HTML |
| Stili inline | style="color: red" | style={{color: 'red'}} | Lo stile è un oggetto JS, non una stringa |
| Un solo elemento radice | — (più tag fratelli OK) | Necessario un wrapper o <></> | JSX ritorna un singolo valore JavaScript |
| Espressioni JavaScript | Non supportate | { espressione } | Le graffe eseguono codice JS dentro il markup |
| Commenti | <!-- commento --> | {/* commento */} | I commenti in JSX sono espressioni JS |
Le graffe { } — il superpotere di JSX
Le graffe dentro JSX aprono una “finestra” su JavaScript: puoi scriverci qualunque espressione valida. Questo è ciò che rende le interfacce React dinamiche.
const titolo = 'Setup ambiente di sviluppo' const data = new Date().toLocaleDateString('it-IT') const tags = ['React', 'Node.js', 'Vite'] return ( <div> {/* variabile stringa */} <h2>{titolo}</h2> {/* espressione */} <p>Creata il {data}</p> {/* ternario: rendering condizionale */} <p>{tags.length > 0 ? 'Ha tag' : 'Nessun tag'}</p> {/* .map() per generare liste di elementi */} <ul> {tags.map(tag => <li key={tag}>{tag}</li>)} </ul> </div> )
Dentro le graffe puoi scrivere espressioni (valori che producono un risultato): variabili, operatori ternari, chiamate a funzioni, .map(). Non puoi scrivere statement: niente if, for, while, const. Se hai bisogno di logica complessa, mettila fuori dal return prima del JSX, oppure usa funzioni helper.
I componenti — gli atomi dell’interfaccia
React è costruito sul concetto di componente: un’unità autonoma e riusabile dell’interfaccia utente. Ogni componente è una funzione JavaScript che riceve dei dati in input (props) e restituisce JSX in output.
Un componente React è una funzione JavaScript il cui nome inizia con la lettera maiuscola, che accetta un unico argomento (props) e restituisce React Elements (JSX). Il nome maiuscolo è fondamentale: React distingue i tag HTML standard (<div>, minuscolo) dai componenti personalizzati (<NoteCard>, maiuscolo) proprio dalla capitalizzazione.
L’idea chiave è la composizione: costruisci interfacce complesse assemblandole da componenti semplici, esattamente come costruisci una casa con i mattoni. Ogni mattone è indipendente, testabile da solo, e riutilizzabile in contesti diversi.
Il primo componente — anatomia completa
Ecco la struttura minima di un componente React funzionale, con ogni parte spiegata:
// 1. Import: tutto ciò che il componente usa dall'esterno import styles from './NoteCard.module.css' // 2. La funzione componente (nome con lettera MAIUSCOLA) function NoteCard({ titolo, contenuto, tag }) { // ← props destrutto // 3. Logica JavaScript (variabili, calcoli, handler) // Tutto ciò che non è JSX va QUI, prima del return const dataFormattata = new Date().toLocaleDateString('it-IT') // 4. Il return: il JSX che verrà renderizzato nel DOM return ( <article className={styles.card}> <span className={styles.tag}>{tag}</span> <h3 className={styles.titolo}>{titolo}</h3> <p className={styles.contenuto}>{contenuto}</p> <time className={styles.data}>{dataFormattata}</time> </article> ) } // 5. Export: rende il componente importabile da altri file export default NoteCard—
Le Props — come i componenti comunicano
Le props (abbreviazione di properties) sono il meccanismo con cui un componente genitore passa dati a un componente figlio. Il flusso è sempre unidirezionale: dall’alto verso il basso nell’albero dei componenti. Un figlio non può modificare le props che riceve — può solo leggerle.
Concettualmente, le props sono identiche ai parametri di una funzione. Quando scrivi <NoteCard titolo="Setup" /> è come chiamare NoteCard({ titolo: "Setup" }).
<NoteCard titolo="Setup ambiente" ← prop stringa tag="React" completata={true} ← prop booleana (graffe!) onElimina={handleElimina} ← prop funzione (callback) />
function NoteCard({ titolo, tag, completata, onElimina }) { // titolo → "Setup ambiente" // tag → "React" // completata → true // onElimina → la funzione del genitore return (...{titolo}...) }
Puoi ricevere le props in due modi: come oggetto intero function NoteCard(props) e poi usarle come props.titolo, oppure destrutturandole direttamente nel parametro function NoteCard({ titolo, tag }). La seconda forma è quasi sempre preferita: è più concisa e rende evidente a colpo d’occhio quali props usa il componente.
La prop speciale: children
Esiste una prop speciale chiamata children che contiene tutto ciò che viene scritto tra i tag di apertura e chiusura del componente. È fondamentale per costruire componenti contenitore generici come card, modal o layout.
// Definizione del componente contenitore function Card({ children, titolo }) { return ( <div className="card"> <h3>{titolo}</h3> <div className="card-body">{children}</div> </div> ) } // Utilizzo: ciò che è "dentro" il tag diventa children <Card titolo="La mia nota"> <p>Questo testo è children</p> <button>Anche questo bottone è children</button> </Card>—
Export default vs named export
Ogni file di componente deve esportare ciò che contiene per renderlo importabile. Esistono due modi, e capire la differenza evita errori di import che possono confondere all’inizio.
// NoteCard.jsx function NoteCard() { ... } export default NoteCard // App.jsx — import import NoteCard from './NoteCard' // oppure qualunque nome! import Scheda from './NoteCard'
Un solo default export per file. L’import può usare qualunque nome (anche diverso dall’originale). Usato per il componente principale del file.
// utils.js export function formattaData(d) { ... } export const COLORI = ['blue', 'red'] // App.jsx — import import { formattaData, COLORI } from './utils' // il nome DEVE corrispondere (salvo alias)
Più named export per file. Il nome nell’import deve corrispondere esattamente (o usare alias con as). Usato per utility, costanti, hook multipli.
Un file di componente React (.jsx) contiene quasi sempre un solo componente esportato come export default. I file di utility (utils.js), costanti e hook personalizzati usano invece named export perché possono esportare più cose dallo stesso file. Segui questa convenzione e il tuo codice sarà immediatamente comprensibile a qualunque sviluppatore React.
Organizzare il progetto in cartelle
Vite genera tutto in src/ senza sotto-cartelle. Va bene per un componente, ma per un progetto reale con 10-20 componenti diventa rapidamente un caos. Adottiamo subito una struttura professionale:
src/ ├── components/ ← componenti riutilizzabili (UI pura) │ ├── Header/ │ │ ├── Header.jsx │ │ └── Header.module.css │ ├── NoteCard/ │ │ ├── NoteCard.jsx │ │ └── NoteCard.module.css │ └── NoteList/ │ ├── NoteList.jsx │ └── NoteList.module.css ├── pages/ ← componenti che rappresentano pagine (una per route) │ └── Home.jsx ├── utils/ ← funzioni di utilità pure (no JSX) │ └── formatters.js ├── App.jsx ├── index.css └── main.jsx
Ogni componente ha la sua sotto-cartella con il file JSX e il relativo file CSS Modules. Questo pattern (chiamato spesso “component colocation”) tiene insieme tutto ciò che riguarda un componente: il markup, lo stile, eventualmente i test. Se elimini la cartella, elimini il componente senza lasciare file CSS orfani in giro.
—Il file lab-02-componenti-jsx.md ti guida passo-passo nel riorganizzare il progetto DevNotes, creare i componenti Header, NoteCard e NoteList con dati statici, e collegare tutto in App.jsx.
- JSX è sintassi estesa di JavaScript, non HTML: viene compilato in
React.createElement()da Vite. Le principali differenze:className, attributi camelCase, tag auto-chiudenti, un solo elemento radice. - Le graffe { } in JSX aprono una finestra su JavaScript: accettano espressioni (variabili, ternari,
.map()), non statement (if,for). - Un componente è una funzione con nome maiuscolo che riceve props e restituisce JSX. La composizione di componenti semplici costruisce interfacce complesse.
- Le props fluiscono sempre dal genitore al figlio (unidirezionale). Vanno destrutto nel parametro della funzione. La prop speciale
childrencontiene il contenuto annidato. - export default per il componente principale del file (uno per file), named export per utility e costanti (più per file).
- La prossima lezione introduce
useStateeuseEffect: rendiamo l’interfaccia interattiva — le note si aggiungono, si eliminano, si filtrano.