React
Composizione invece di eredità nei componenti
Un componente React si specializza passando children, props e oggetti, non estendendo una classe: le props arrivano a ogni render, la gerarchia no.
In sintesi
- L’eredità fissa la variante alla definizione della classe; le props la scelgono a ogni render.
- Una mappa di varianti sostituisce N sottoclassi con una funzione e un oggetto costante.
- Definire un componente dentro un altro crea un nuovo tipo a ogni render e rimonta il sottoalbero.
Un componente React si specializza componendo children, props e oggetti, non estendendo una classe: l’eredità fissa la gerarchia alla definizione, mentre le props arrivano a ogni render e possono cambiare.
Perché extends non porta lontano
Una classe base Button con tre sottoclassi copre poche varianti. Con quattro assi indipendenti, dimensione, tono, icona e stato, le combinazioni crescono e ogni nuova combinazione chiede un nuovo file da mantenere.
La stessa cosa con una prop
const variants = {
primary: "bg-blue-600 text-white",
danger: "bg-red-600 text-white",
ghost: "bg-transparent text-slate-900",
};
function Button({ variant = "primary", children, ...rest }) {
return (
{children}
);
}
Una funzione, un oggetto di varianti, nessuna gerarchia. Aggiungere un tono significa aggiungere una chiave, non una classe che eredita dalla precedente.
Tre forme di composizione
- Props di contenuto:
children,headerefooterdecidono cosa mostrare dentro la struttura. - Props di comportamento:
onSelectorenderItemdecidono cosa succede quando l’utente agisce. - Composizione di componenti:
DialogpiùDialogTitlepiùDialogBodycostruiscono un vocabolario, non una catena di classi.
Lo slot come punto di estensione
Il contenitore decide il layout, il chiamante decide il contenuto: è la stessa divisione di responsabilità che l’eredità prova a ottenere con i metodi protetti, senza il vincolo della classe.
function Card({ header, children, footer }) {
return (
{header && {header}}
{children}
{footer && {footer}}
);
}
Chi usa Card non estende niente: passa tre pezzi e il componente li colloca. Se un giorno serve una quarta zona, si aggiunge una prop con un nome, non un livello di ereditarietà.
Il comando
La regola che intercetta il caso più frequente, un componente definito dentro un altro, arriva dal plugin ESLint di React; la configurazione seguente la attiva da riga di comando.
npm install --save-dev eslint eslint-plugin-react
npx eslint src --rule '{"react/no-unstable-nested-components":"error"}'
Record vivi nel catalogo
Tre record composti da slot: card-stack impila schede che riceve, accordion-05 accetta un elemento per pannello, nested-dialog annida un dialogo in un altro. Sono record fra i 111.985 componenti e i 1.556 blocchi.
Fonte e licenza
React e eslint-plugin-react sono distribuiti con licenza MIT. La pagina storica della documentazione React dedicata a composizione ed ereditarietà dice la stessa cosa in una riga: React ha un modello potente di composizione e non consiglia l’eredità per riusare il codice fra componenti.
La limitazione
La composizione sposta la decisione sul chiamante: un componente con cinque slot non garantisce nessun layout predefinito, e chi lo usa può produrre combinazioni che nessun test ha previsto. Inoltre un componente definito dentro il render cambia identità a ogni passaggio e rimonta il sottoalbero: è il motivo per cui la regola ESLint esiste.
Dove continuare
Le basi sono in che cos’è React; quando una prop identica evita un render è in React.memo; il confronto con gli slot nominati di Vue è in slot nominati in Vue 3.
Domande frequenti
Composizione ed ereditarietà sono alternative?
Per i componenti React sì. Una classe può estendere React.Component, ma specializzarla per ereditarietà non serve: le props coprono le varianti senza creare una nuova classe.
Quando conviene ancora una classe?
Solo per un error boundary, perché componentDidCatch non ha un equivalente a hook. Per tutto il resto una funzione con hook è la forma consigliata.
Che differenza c’è fra children e una prop normale?
Nessuna nel motore: children è la prop che JSX riempie con il contenuto dell’elemento. Cambia solo la sintassi con cui la passi.
Una mappa di varianti è più lenta di una classe?
No: l’oggetto è costante e la ricerca è O(1). Il costo vero è il numero di classi CSS generate, non il lookup.
Come si testa un componente composto?
Dal punto di vista del chiamante: si passano i contenuti, si verifica che compaiano nel DOM e che gli slot vuoti non lascino contenitori inutili.