Componenti Funzionali
Cosa Sono i Componenti Funzionali
In React, un componente funzionale è una funzione JavaScript che accetta props come argomento e restituisce JSX. Dal rilascio degli Hooks in React 16.8, i componenti funzionali possono gestire stato e side effects, rendendo i componenti a classe praticamente obsoleti.
// Il componente funzionale più semplice
function Saluto() {
return <h1>Ciao, mondo!</h1>;
}
Un componente funzionale è valido quando:
- Il nome inizia con una lettera maiuscola (PascalCase)
- Restituisce JSX,
null, o un tipo renderizzabile da React - Accetta un singolo argomento
props(opzionale)
Sintassi di Dichiarazione
Esistono diversi modi per dichiarare un componente funzionale.
Function Declaration
function Benvenuto({ nome }) {
return <h1>Benvenuto, {nome}!</h1>;
}
Arrow Function
const Benvenuto = ({ nome }) => {
return <h1>Benvenuto, {nome}!</h1>;
};
Arrow Function con Return Implicito
const Benvenuto = ({ nome }) => <h1>Benvenuto, {nome}!</h1>;
// Per JSX multi-linea, usa le parentesi tonde
const Card = ({ titolo, descrizione }) => (
<div className="card">
<h2>{titolo}</h2>
<p>{descrizione}</p>
</div>
);
Quale Sintassi Scegliere?
La scelta è principalmente stilistica, ma ci sono differenze pratiche:
| Caratteristica | Function Declaration | Arrow Function |
|---|---|---|
| Hoisting | Sì | No |
| Nome nel DevTools | Automatico | Serve la variabile |
| Brevità | Media | Alta (return implicito) |
| Consistenza | Standard | Preferita in molti team |
La raccomandazione è scegliere uno stile e mantenerlo coerente in tutto il progetto.
Export dei Componenti
Export Default
Ogni file può avere un solo export default. È lo standard per i componenti principali di un file.
// Dichiarazione e export separati
function PaginaHome() {
return <h1>Home</h1>;
}
export default PaginaHome;
// Export default inline (function declaration)
export default function PaginaHome() {
return <h1>Home</h1>;
}
// Import (il nome può essere qualsiasi)
import PaginaHome from "./PaginaHome";
import Home from "./PaginaHome"; // Funziona ugualmente
Named Export
Un file può avere molteplici named export. Utile per esportare componenti secondari o utility.
// components/Card.tsx
export function Card({ children }) {
return <div className="card">{children}</div>;
}
export function CardHeader({ titolo }) {
return <h2 className="card-header">{titolo}</h2>;
}
export function CardBody({ children }) {
return <div className="card-body">{children}</div>;
}
// Import (il nome DEVE corrispondere)
import { Card, CardHeader, CardBody } from "./Card";
Re-export con File Index
Un pattern comune è creare file index.ts per raggruppare gli export di una cartella.
// components/ui/index.ts
export { Button } from "./Button";
export { Card, CardHeader, CardBody } from "./Card";
export { Input } from "./Input";
export { Modal } from "./Modal";
// Ora puoi importare tutto da un unico percorso
import { Button, Card, Input, Modal } from "@/components/ui";
Naming Conventions
Nomi dei Componenti
I componenti usano PascalCase, sia per il nome della funzione che per il file.
// CORRETTO
function ProfiloUtente() { ... }
function ListaArticoli() { ... }
function BottonePrimario() { ... }
// SCORRETTO
function profiloUtente() { ... } // camelCase
function profilo_utente() { ... } // snake_case
function PROFILO() { ... } // SCREAMING_CASE
Nomi dei File
La convenzione più diffusa è usare PascalCase per i file dei componenti.
components/
├── ProfiloUtente.tsx
├── ListaArticoli.tsx
├── BottonePrimario.tsx
└── ui/
├── Button.tsx
├── Card.tsx
└── Input.tsx
Alcuni progetti usano kebab-case per i nomi dei file, che è altrettanto valido purché sia coerente.
components/
├── profilo-utente.tsx
├── lista-articoli.tsx
└── bottone-primario.tsx
Nomi delle Props
Le props usano camelCase, coerentemente con le convenzioni JavaScript.
function Articolo({
titoloArticolo,
dataCreazione,
isPublicato,
onClickModifica,
numeroPagine,
}) {
// ...
}
Organizzazione dei File
Un Componente per File
La regola generale è avere un componente principale per file, con eventuali componenti helper privati nello stesso file.
// ListaProdotti.tsx
// Componente helper privato (non esportato)
function ElementoProdotto({ prodotto }) {
return (
<li className="prodotto">
<span>{prodotto.nome}</span>
<span>{prodotto.prezzo} EUR</span>
</li>
);
}
// Componente principale (esportato)
export default function ListaProdotti({ prodotti }) {
return (
<ul>
{prodotti.map((prodotto) => (
<ElementoProdotto key={prodotto.id} prodotto={prodotto} />
))}
</ul>
);
}
Pattern Cartella per Componente
Per componenti complessi, organizza tutto in una cartella dedicata.
ProfiloUtente/
├── ProfiloUtente.tsx # Componente principale
├── ProfiloUtente.module.css # Stili
├── ProfiloUtente.test.tsx # Test
├── AvatarProfilo.tsx # Sotto-componente
├── useProfilo.ts # Hook personalizzato
├── types.ts # Tipi TypeScript
└── index.ts # Re-export
// ProfiloUtente/index.ts
export { default } from "./ProfiloUtente";
export type { ProfiloProps } from "./types";
Componenti Puri e Regole
React si aspetta che i componenti siano funzioni pure rispetto alle props: dati gli stessi input, devono restituire lo stesso output.
// CORRETTO: componente puro
function Saluto({ nome }) {
return <h1>Ciao, {nome}!</h1>;
}
// SCORRETTO: effetto collaterale nel render
function Contatore() {
let count = 0;
count++; // Modifica una variabile ad ogni render
return <p>Conteggio: {count}</p>;
}
// SCORRETTO: modifica le props
function Formattatore({ lista }) {
lista.sort(); // MAI mutare le props!
return <ul>{lista.map((item) => <li key={item}>{item}</li>)}</ul>;
}
// CORRETTO: crea una copia prima di modificare
function Formattatore({ lista }) {
const listaOrdinata = [...lista].sort();
return <ul>{listaOrdinata.map((item) => <li key={item}>{item}</li>)}</ul>;
}
Componente che Restituisce null
Un componente può restituire null per non renderizzare nulla.
function MessaggioErrore({ errore }) {
if (!errore) {
return null;
}
return (
<div className="errore" role="alert">
<p>{errore}</p>
</div>
);
}
Componenti con TypeScript
TypeScript aggiunge sicurezza sui tipi alle props dei componenti.
interface CardProps {
titolo: string;
descrizione: string;
immagine?: string; // Prop opzionale
onClick?: () => void; // Callback opzionale
variante?: "default" | "evidenziata";
}
function Card({
titolo,
descrizione,
immagine,
onClick,
variante = "default",
}: CardProps) {
return (
<div className={`card card-${variante}`} onClick={onClick}>
{immagine && <img src={immagine} alt={titolo} />}
<h2>{titolo}</h2>
<p>{descrizione}</p>
</div>
);
}
Best Practices
Mantieni i Componenti Piccoli
Se un componente supera le 100-150 righe, probabilmente va suddiviso in componenti più piccoli.
// Troppo grande - suddividi!
function Dashboard() {
return (
<div>
<HeaderDashboard />
<GraficoVendite />
<TabellaOrdini />
<PannelloNotifiche />
</div>
);
}
Separa Logica e Presentazione
Estrai la logica complessa in custom hooks, mantenendo i componenti focalizzati sulla presentazione.
// Hook con la logica
function useProdotti() {
const [prodotti, setProdotti] = useState([]);
const [caricamento, setCaricamento] = useState(true);
useEffect(() => {
fetch("/api/prodotti")
.then((res) => res.json())
.then((data) => {
setProdotti(data);
setCaricamento(false);
});
}, []);
return { prodotti, caricamento };
}
// Componente focalizzato sulla presentazione
function ListaProdotti() {
const { prodotti, caricamento } = useProdotti();
if (caricamento) return <p>Caricamento...</p>;
return (
<ul>
{prodotti.map((p) => (
<li key={p.id}>{p.nome}</li>
))}
</ul>
);
}
Riepilogo
I componenti funzionali sono il mattone fondamentale di React. Usa PascalCase per i nomi, scegli tra function declaration e arrow function in modo coerente, organizza i file in modo logico e mantieni i componenti piccoli e puri. Il prossimo passo è capire come passare dati tra componenti tramite le props.