Il prop drilling consiste nel passare una prop attraverso componenti che non la usano, solo perché un componente più in basso nell'albero possa farlo. Qui user viaggia da App attraverso Page e Sidebar per raggiungere Avatar, l'unico componente che lo legge.
Quattro livelli, e solo l'ultimo si interessa di user. La Console mostra Page e Sidebar eseguiti per un valore che non mostrano mai. Clicca il pulsante e registrano di nuovo.
Perché è un problema
Due o tre livelli vanno bene. Il prop drilling diventa un problema man mano che l'albero cresce:
- I componenti intermedi trasportano props che non usano.
PageeSidebarora hanno una propusernella loro firma, quindi non puoi leggerli senza chiederti a cosa serva. - Ogni modifica tocca ogni livello. Aggiungi
user.avatarUrlo un secondo valore comeonLogout, e modifichi ogni componente lungo il percorso, non solo le due estremità. - Spostare un componente spezza la catena. Sposta
Avatarin un ramo diverso e devi far passareuserlungo un nuovo percorso. - È facile perdere una prop. Dimentica di inoltrarla in un livello e il componente in profondità riceve
undefined, senza alcun errore nel livello che l'ha persa.
Provaci: aggiungi in App una funzione onLogout che Avatar dovrebbe chiamare. Modificherai tutti e quattro i componenti.
Soluzione 1: composizione con children
Questa è la soluzione che spesso si salta. Invece di passare dati verso il basso perché un componente in profondità possa renderizzarli, lascia che il componente che possiede i dati renderizzi lui stesso il componente in profondità, e passa l'elemento già pronto verso il basso come children (o qualsiasi altra prop). I livelli intermedi renderizzano uno slot e non vedono mai user.
Page e Sidebar non sanno più che esiste un utente. App crea Avatar dove vive user e lo passa verso il basso come elemento pronto. Componenti di layout come questi diventano riutilizzabili, e aggiungere onLogout ora è una modifica solo in App e Avatar. Entrambi registrano ancora un log a ogni clic, perché App passa loro un nuovo elemento ogni volta che si renderizza: la composizione semplifica il codice, non il numero di rendering. La pagina su children tratta altri schemi con gli slot.
La composizione funziona quando il componente che possiede i dati può anche decidere l'aspetto della parte in profondità. Non aiuta quando il componente in profondità sta dentro una libreria o una struttura che il proprietario non renderizza.
Soluzione 2: context
Quando molti componenti a profondità diverse hanno bisogno dello stesso valore (il tema, l'utente che ha effettuato l'accesso, la lingua attuale), mettilo nel context. Qualsiasi componente sotto il provider lo legge con useContext, e nulla in mezzo cambia.
Avatar e Greeting leggono l'utente da due profondità diverse, e Page e Sidebar non ricevono props. Aggiungi un terzo lettore ovunque sotto Page e funziona senza toccare il resto. Il prezzo: il flusso dei dati non è più visibile nelle props, e ogni lettore esegue un nuovo render quando il valore cambia. La pagina su useContext tratta provider, valori predefiniti e quei nuovi render.
Soluzione 3: una libreria di stato
Per uno stato grande dell'app che molti componenti leggono e che cambia spesso (un carrello, un editor di documenti, dati in tempo reale), una libreria di stato come Redux Toolkit, Zustand o Jotai permette a ogni componente di iscriversi solo alla porzione che usa. Così si evitano sia il drilling sia il costo di "ogni consumer esegue un nuovo render" di un unico grande context. È anche un'altra dipendenza e un altro insieme di concetti, quindi ricorrici quando le prime due soluzioni non bastano più, non per sistemare una prop che attraversa tre livelli.
// Zustand, for comparison (not available in the editor here)
const useCart = create((set) => ({
items: [],
add: (item) => set((s) => ({ items: [...s.items, item] })),
}));
function CartCount() {
const count = useCart((s) => s.items.length); // renders only when the count changes
return <span>{count}</span>;
}
Quando il prop drilling va bene
Passare props è il modo in cui scorrono i dati in React, ed è lo schema più facile da leggere: puoi seguire un valore dall'alto in basso guardando il JSX. Una prop che passa attraverso uno o due componenti che ne usano una parte, o attraverso una breve catena di componenti strettamente correlati, non è un problema da risolvere. Solleva lo stato al genitore comune più vicino, passalo verso il basso e cambia approccio solo quando i livelli intermedi iniziano a trasportare props di cui non hanno alcun bisogno.
Domande frequenti
Cos'è il prop drilling in React?
Passare una prop attraverso diversi livelli di componenti che non la usano, solo perché un componente in profondità nell'albero possa leggerla. Ogni componente intermedio accetta la prop e la inoltra.
Il prop drilling è un male?
Non di per sé. Passare un valore due o tre livelli più in basso è esplicito e facile da seguire. Diventa un problema quando molti livelli trasportano props che non usano mai, così ogni rinomina o nuovo campo tocca file che non c'entrano nulla.
Come evito il prop drilling?
Prova prima la composizione: lascia che il genitore costruisca il componente in profondità e lo passi verso il basso come children o un'altra prop, così i livelli intermedi non vedono mai i dati. Se molti componenti a profondità diverse hanno bisogno del valore, usa il context. Per uno stato grande che cambia spesso, valuta una libreria di stato.
Il context è l'unica soluzione al prop drilling?
No, e spesso non è la migliore. La composizione con children elimina il drilling senza aggiungere un context, e mantiene il flusso dei dati visibile nel codice.