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, header e footer decidono cosa mostrare dentro la struttura.
  • Props di comportamento: onSelect o renderItem decidono cosa succede quando l’utente agisce.
  • Composizione di componenti: Dialog più DialogTitle più DialogBody costruiscono 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"}'

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.