Children ti permette di manipolare e trasformare il JSX che hai ricevuto come prop children.
const mappedChildren = Children.map(children, child =>
<div className="Row">
{child}
</div>
);Reference
Children.count(children)
Chiama Children.count(children) per contare il numero di children nella struttura dati children.
import { Children } from 'react';
function RowList({ children }) {
return (
<>
<h1>Total rows: {Children.count(children)}</h1>
...
</>
);
}Parameters
children: Il valore della propchildrenricevuto dal tuo componente.
Returns
Il numero di nodi all’interno di questi children.
Caveats
- I nodi vuoti (
null,undefinede valori booleani), le stringhe, i numeri e gli elementi React contano come nodi individuali. Gli array non contano come nodi individuali, ma i loro children sì. L’attraversamento non va più a fondo degli elementi React: non vengono renderizzati e i loro children non vengono attraversati. I Fragment non vengono attraversati.
Children.forEach(children, fn, thisArg?)
Chiama Children.forEach(children, fn, thisArg?) per eseguire del codice per ogni child nella struttura dati children.
import { Children } from 'react';
function SeparatorList({ children }) {
const result = [];
Children.forEach(children, (child, index) => {
result.push(child);
result.push(<hr key={index} />);
});
// ...Parameters
children: Il valore della propchildrenricevuto dal tuo componente.fn: La funzione che vuoi eseguire per ogni child, simile al callback del metodoforEachdegli array. Verrà chiamata con il child come primo argomento e il suo indice come secondo argomento. L’indice parte da0e si incrementa a ogni chiamata.- optional
thisArg: Il valore dithiscon cui la funzionefndovrebbe essere chiamata. Se omesso, èundefined.
Returns
Children.forEach restituisce undefined.
Caveats
- I nodi vuoti (
null,undefinede valori booleani), le stringhe, i numeri e gli elementi React contano come nodi individuali. Gli array non contano come nodi individuali, ma i loro children sì. L’attraversamento non va più a fondo degli elementi React: non vengono renderizzati e i loro children non vengono attraversati. I Fragment non vengono attraversati.
Children.map(children, fn, thisArg?)
Chiama Children.map(children, fn, thisArg?) per mappare o trasformare ogni child nella struttura dati children.
import { Children } from 'react';
function RowList({ children }) {
return (
<div className="RowList">
{Children.map(children, child =>
<div className="Row">
{child}
</div>
)}
</div>
);
}Parameters
children: Il valore della propchildrenricevuto dal tuo componente.fn: La funzione di mapping, simile al callback del metodomapdegli array. Verrà chiamata con il child come primo argomento e il suo indice come secondo argomento. L’indice parte da0e si incrementa a ogni chiamata. Devi restituire un nodo React da questa funzione. Può essere un nodo vuoto (null,undefinedo un valore booleano), una stringa, un numero, un elemento React o un array di altri nodi React.- optional
thisArg: Il valore dithiscon cui la funzionefndovrebbe essere chiamata. Se omesso, èundefined.
Returns
Se children è null o undefined, restituisce lo stesso valore.
Altrimenti, restituisce un array piatto composto dai nodi che hai restituito dalla funzione fn. L’array restituito conterrà tutti i nodi che hai restituito, tranne null e undefined.
Caveats
-
I nodi vuoti (
null,undefinede valori booleani), le stringhe, i numeri e gli elementi React contano come nodi individuali. Gli array non contano come nodi individuali, ma i loro children sì. L’attraversamento non va più a fondo degli elementi React: non vengono renderizzati e i loro children non vengono attraversati. I Fragment non vengono attraversati. -
Se restituisci un elemento o un array di elementi con key da
fn, le key degli elementi restituiti verranno combinate automaticamente con la key dell’elemento originale corrispondente dachildren. Quando restituisci più elementi dafnin un array, le loro key devono essere uniche solo localmente tra loro.
Children.only(children)
Chiama Children.only(children) per verificare che children rappresenti un singolo elemento React.
function Box({ children }) {
const element = Children.only(children);
// ...Parameters
children: Il valore della propchildrenricevuto dal tuo componente.
Returns
Se children è un elemento valido, restituisce quell’elemento.
Altrimenti, lancia un errore.
Caveats
- Questo metodo lancia sempre un errore se passi un array (come il valore restituito da
Children.map) comechildren. In altre parole, impone chechildrensia un singolo elemento React, non che sia un array con un singolo elemento.
Children.toArray(children)
Chiama Children.toArray(children) per creare un array dalla struttura dati children.
import { Children } from 'react';
export default function ReversedList({ children }) {
const result = Children.toArray(children);
result.reverse();
// ...Parameters
children: Il valore della propchildrenricevuto dal tuo componente.
Returns
Restituisce un array piatto di elementi in children.
Caveats
- I nodi vuoti (
null,undefinede valori booleani) verranno omessi nell’array restituito. Le key degli elementi restituiti verranno calcolate dalle key degli elementi originali e dal loro livello di annidamento e posizione. Questo garantisce che l’appiattimento dell’array non introduca cambiamenti nel comportamento.
Usage
Trasformare i children
Per trasformare il JSX dei children che il tuo componente riceve come prop children, chiama Children.map:
import { Children } from 'react';
function RowList({ children }) {
return (
<div className="RowList">
{Children.map(children, child =>
<div className="Row">
{child}
</div>
)}
</div>
);
}Nell’esempio sopra, RowList avvolge ogni child che riceve in un contenitore <div className="Row">. Ad esempio, supponiamo che il componente genitore passi tre tag <p> come prop children a RowList:
<RowList>
<p>This is the first item.</p>
<p>This is the second item.</p>
<p>This is the third item.</p>
</RowList>Poi, con l’implementazione di RowList sopra, il risultato finale renderizzato sarà simile a questo:
<div className="RowList">
<div className="Row">
<p>This is the first item.</p>
</div>
<div className="Row">
<p>This is the second item.</p>
</div>
<div className="Row">
<p>This is the third item.</p>
</div>
</div>Children.map è simile a trasformare array con map(). La differenza è che la struttura dati children è considerata opaca. Ciò significa che, anche se a volte è un array, non dovresti assumere che lo sia o che abbia un altro tipo di dato particolare. Per questo motivo, se devi trasformarla, dovresti usare Children.map.
import { Children } from 'react'; export default function RowList({ children }) { return ( <div className="RowList"> {Children.map(children, child => <div className="Row"> {child} </div> )} </div> ); }
Approfondimento
In React, la prop children è considerata una struttura dati opaca. Ciò significa che non dovresti fare affidamento su come è strutturata. Per trasformare, filtrare o contare i children, dovresti usare i metodi Children.
In pratica, la struttura dati children è spesso rappresentata internamente come un array. Tuttavia, se c’è un solo child, React non creerà un array aggiuntivo perché ciò porterebbe a un overhead di memoria non necessario. Finché usi i metodi Children invece di ispezionare direttamente la prop children, il tuo codice non si romperà anche se React cambia il modo in cui la struttura dati è effettivamente implementata.
Anche quando children è un array, Children.map ha un comportamento speciale utile. Ad esempio, Children.map combina le key sugli elementi restituiti con le key sui children che gli hai passato. Questo garantisce che i children JSX originali non “perdano” le key anche se vengono avvolti come nell’esempio sopra.
Eseguire del codice per ogni child
Chiama Children.forEach per iterare su ogni child nella struttura dati children. Non restituisce alcun valore ed è simile al metodo forEach degli array. Puoi usarlo per eseguire logica personalizzata, come costruire il tuo array.
import { Children } from 'react'; export default function SeparatorList({ children }) { const result = []; Children.forEach(children, (child, index) => { result.push(child); result.push(<hr key={index} />); }); result.pop(); // Remove the last separator return result; }
import { Children } from 'react'; export default function RowList({ children }) { return ( <div className="RowList"> <h1 className="RowListHeader"> Total rows: {Children.count(children)} </h1> {Children.map(children, child => <div className="Row"> {child} </div> )} </div> ); }
Convertire i children in un array
Chiama Children.toArray(children) per trasformare la struttura dati children in un normale array JavaScript. Questo ti permette di manipolare l’array con i metodi array integrati come filter, sort o reverse.
import { Children } from 'react'; export default function ReversedList({ children }) { const result = Children.toArray(children); result.reverse(); return result; }
Alternatives
Esporre più componenti
Manipolare i children con i metodi Children spesso porta a codice fragile. Quando passi children a un componente in JSX, di solito non ti aspetti che il componente manipoli o trasformi i singoli children.
Quando puoi, cerca di evitare l’uso dei metodi Children. Ad esempio, se vuoi che ogni child di RowList sia avvolto in <div className="Row">, esporta un componente Row e avvolgi manualmente ogni riga così:
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList> <Row> <p>This is the first item.</p> </Row> <Row> <p>This is the second item.</p> </Row> <Row> <p>This is the third item.</p> </Row> </RowList> ); }
A differenza dell’uso di Children.map, questo approccio non avvolge automaticamente ogni child. Tuttavia, questo approccio ha un vantaggio significativo rispetto al precedente esempio con Children.map perché funziona anche se continui a estrarre altri componenti. Ad esempio, funziona ancora se estrai il tuo componente MoreRows:
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList> <Row> <p>This is the first item.</p> </Row> <MoreRows /> </RowList> ); } function MoreRows() { return ( <> <Row> <p>This is the second item.</p> </Row> <Row> <p>This is the third item.</p> </Row> </> ); }
Questo non funzionerebbe con Children.map perché “vedrebbe” <MoreRows /> come un singolo child (e una singola riga).
Accettare un array di oggetti come prop
Puoi anche passare esplicitamente un array come prop. Ad esempio, questo RowList accetta un array rows come prop:
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList rows={[ { id: 'first', content: <p>This is the first item.</p> }, { id: 'second', content: <p>This is the second item.</p> }, { id: 'third', content: <p>This is the third item.</p> } ]} /> ); }
Poiché rows è un normale array JavaScript, il componente RowList può usare metodi array integrati come map su di esso.
Questo pattern è particolarmente utile quando vuoi poter passare più informazioni come dati strutturati insieme ai children. Nell’esempio sotto, il componente TabSwitcher riceve un array di oggetti come prop tabs:
import TabSwitcher from './TabSwitcher.js'; export default function App() { return ( <TabSwitcher tabs={[ { id: 'first', header: 'First', content: <p>This is the first item.</p> }, { id: 'second', header: 'Second', content: <p>This is the second item.</p> }, { id: 'third', header: 'Third', content: <p>This is the third item.</p> } ]} /> ); }
A differenza del passaggio dei children come JSX, questo approccio ti permette di associare alcuni dati extra come header a ogni elemento. Poiché lavori direttamente con tabs, ed è un array, non hai bisogno dei metodi Children.
Chiamare una render prop per personalizzare la renderizzazione
Invece di produrre JSX per ogni singolo elemento, puoi anche passare una funzione che restituisce JSX e chiamarla quando necessario. In questo esempio, il componente App passa una funzione renderContent al componente TabSwitcher. Il componente TabSwitcher chiama renderContent solo per la tab selezionata:
import TabSwitcher from './TabSwitcher.js'; export default function App() { return ( <TabSwitcher tabIds={['first', 'second', 'third']} getHeader={tabId => { return tabId[0].toUpperCase() + tabId.slice(1); }} renderContent={tabId => { return <p>This is the {tabId} item.</p>; }} /> ); }
Una prop come renderContent si chiama render prop perché è una prop che specifica come renderizzare un pezzo dell’interfaccia utente. Tuttavia, non c’è nulla di speciale: è una prop normale che per caso è una funzione.
Le render props sono funzioni, quindi puoi passare loro informazioni. Ad esempio, questo componente RowList passa l’id e l’index di ogni riga alla render prop renderRow, che usa index per evidenziare le righe pari:
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList rowIds={['first', 'second', 'third']} renderRow={(id, index) => { return ( <Row isHighlighted={index % 2 === 0}> <p>This is the {id} item.</p> </Row> ); }} /> ); }
Questo è un altro esempio di come componenti genitore e figlio possono cooperare senza manipolare i children.
Troubleshooting
Passo un componente personalizzato, ma i metodi Children non mostrano il suo risultato di renderizzazione
Supponiamo di passare due children a RowList così:
<RowList>
<p>First item</p>
<MoreRows />
</RowList>Se esegui Children.count(children) dentro RowList, otterrai 2. Anche se MoreRows renderizza 10 elementi diversi, o se restituisce null, Children.count(children) sarà comunque 2. Dal punto di vista di RowList, “vede” solo il JSX che ha ricevuto. Non “vede” l’interno del componente MoreRows.
Questa limitazione rende difficile estrarre un componente. Per questo motivo le alternative sono preferite all’uso di Children.