Children
te permite manipular y transformar el JSX que recibes como la prop children
.
const mappedChildren = Children.map(children, child =>
<div className="Row">
{child}
</div>
);
Referencia
Children.count(children)
Llama Children.count(children)
para contar el número de hijos en la estructura de datos children
.
import { Children } from 'react';
function RowList({ children }) {
return (
<>
<h1>Filas totales: {Children.count(children)}</h1>
...
</>
);
}
Parámetros
children
: el valor de la propchildren
que recibe tu componente.
Devuelve
El número de nodos dentro de estos hijos (children
).
Advertencias
- Nodos vacíos (
null
,undefined
, y booleanos), strings, números, y elementos de React cuentan como nodos individuales. Los arrays no cuentan como nodos individuales, pero sus hijos sí. El recorrido no va más profundo que al nivel de los elementos de React: no se renderizan, y no se recorren sus hijos. Los Fragmentos no se recorren.
Children.forEach(children, fn, thisArg?)
Llama a Children.forEach(children, fn, thisArg?)
para correr algún código por cada hijo en la estructura de datos children
.
import { Children } from 'react';
function SeparatorList({ children }) {
const result = [];
Children.forEach(children, (child, index) => {
result.push(child);
result.push(<hr key={index} />);
});
// ...
Parámetros
children
: El valor de la propchildren
que recibe por tu componente.fn
: La función que deseas ejecutar para cada hijo, similar al callback del método arrayforEach
. Se llamará con el hijo como primer argumento y su índice como segundo argumento. El índice empieza en0
y se incrementa por cada llamada.- opcional
thisArg
: El valorthis
con el que se debe llamar a la funciónfn
. Si se omite, esundefined
.
Devuelve
Children.forEach
devuelve undefined
.
Advertencias
- Nodos vacíos (
null
,undefined
, y booleanos), strings, números, y elementos de React cuentan como nodos individuales. Los arrays no cuentan como nodos individuales, pero sus hijos si. El recorrido no va más profundo que al nivel de los elementos de React: no se renderizan, y sus hijos no se recorren. Los Fragmentos no se recorren.
Children.map(children, fn, thisArg?)
Llamar Children.map(children, fn, thisArg?)
para mapear o transformar cada hijo en la estructura de datos children
.
import { Children } from 'react';
function RowList({ children }) {
return (
<div className="RowList">
{Children.map(children, child =>
<div className="Row">
{child}
</div>
)}
</div>
);
}
Parámetros
children
: El valor de la propchildren
que recibe por tu componente.fn
: La función de mapeo, similar al callback del métodomap
de un array. Se llamará con el hijo como primer argumento y su índice como segundo argumento. El índice empieza en0
y se incrementa por cada llamada. Necesitas devolver un nodo de React de esta función. Puede ser un nodo vacío (null
,undefined
, o un booleano), un string, un número, un elemento de React, o un array de otros nodos de React.- opcional
thisArg
: El valorthis
con el que se debe llamar a la funciónfn
. Si se omite, esundefined
.
Devuelve
Si children
es null
o undefined
, devuelve el mismo valor.
De lo contrario, devuelve un array plano que consta de los nodos que ha devuelto de la función fn
. El array devuelto contendrá todos los nodos que devolviste excepto por null
y undefined
.
Advertencias
-
Nodos vacíos (
null
,undefined
, y booleanos), strings, números, y elementos de React cuentan como nodos individuales. Los arrays no cuentan como nodos individuales, pero sus hijos sí. El recorrido no va más profundo que al nivel de los elementos de React: no se renderizan, y no se recorren sus hijos. Los Fragmentos no se recorren. -
Si devuelve un elemento o un array de elementos con keys desde
fn
, las keys de los elementos devueltos se combinarán automáticamente con la clave del elemento original correspondiente dechildren
. Cuando devuelves múltiples elementos desdefn
en un array, sus keys solo necesitan ser únicas localmente entre sí.
Children.only(children)
Llamar Children.only(children)
para comprobar que children
representa un solo elemento de React.
function Box({ children }) {
const element = Children.only(children);
// ...
Parámetros
children
: El valor de la propchildren
que recibe tu componente.
Devuelve
Si children
es un elemento válido, devuelve ese elemento.
De lo contrario, lanza un error.
Advertencias
- Este método siempre lanza un error si pasas un array (como el valor de retorno de
Children.map
) comochildren
. En otras palabras, hace cumplir quechildren
es un solo elemento de React, no que sea un array con un solo elemento.
Children.toArray(children)
Llama Children.toArray(children)
para crear un array a partir de la estructura de datos children
.
import { Children } from 'react';
export default function ReversedList({ children }) {
const result = Children.toArray(children);
result.reverse();
// ...
Parámetros
children
: El valor de la propchildren
que recibe tu componente.
Devuelve
Devuelve un array plano de los elementos de children
.
Advertencias
- Nodos vacíos (
null
,undefined
, y booleanos) se omitirán en el array devuelto. Las keys de los elementos devueltos se calcularán a partir de las keys de los elementos originales y su nivel de anidamiento y posición. Esto asegura que aplanar el array no introduzca cambios en el comportamiento.
Uso
Transformar children
Para transformar el JSX de tu componente que recibe como la prop children
, llama a Children.map
:
import { Children } from 'react';
function RowList({ children }) {
return (
<div className="RowList">
{Children.map(children, child =>
<div className="Row">
{child}
</div>
)}
</div>
);
}
En el ejemplo anterior, RowList
envuelve cada hijo que recibe en un contenedor <div className="Row>
. Por ejemplo, digamos que el componente padre pasa tres etiquetas <p>
como la prop children
a RowList
:
<RowList>
<p>Este es el primer elemento.</p>
<p>Este es el segundo elemento.</p>
<p>Este es el tercer elemento.</p>
</RowList>
Después, con la implementación anterior de RowList
, el resultado final renderizado se verá así:
<div className="RowList">
<div className="Row">
<p>Este es el primer elemento.</p>
</div>
<div className="Row">
<p>Este es el segundo elemento.</p>
</div>
<div className="Row">
<p>Este es el tercer elemento.</p>
</div>
</div>
Children.map
es similar a la transformación de arrays con map()
. La diferencia es que la estructura de datos de children
se considera opaca. Esto significa que incluso si a veces es un array, no debes asumir que es un array o cualquier otro tipo de datos en particular. Esta es la razón por la que debes usar Children.map
si necesitas transformarla.
import { Children } from 'react'; export default function RowList({ children }) { return ( <div className="RowList"> {Children.map(children, child => <div className="Row"> {child} </div> )} </div> ); }
Deep Dive
En React, la prop children
se considera una estructura de datos opaca. Esto significa que no debes confiar en cómo está estructurada. Para transformar, filtrar, o contar children, deberías usar los métodos Children
.
En la practica, la estructura de datos children
a menudo se representa como un array internamente. Sin embargo, si solo hay un hijos, entonces React no creará un array extra ya que esto conduciría a una sobrecarga de memoria innecesaria. Siempre y cuando uses los métodos Children
en lugar de hacer una introspección directa de los prop children
, tú código no se romperá incluso si React cambia la forma en que se implementa realmente la estructura de datos.
Incluso cuando children
es un array, Children.map
tiene un comportamiento especial útil. Por ejemplo, Children.map
combina las keys en los elementos devueltos en las keys del children
que le has pasado. Esto asegura que los hijos JSX originales no «pierdan» las keys incluso si se envuelven como en el ejemplo anterior.
Ejecutar un código para cada hijo
Llama a Children.forEach
para iterar sobre cada hijo en la estructura de datos children
. No devuelve ningún valor y es similar al método forEach
de un array. Puedes usarlo para ejecutar una lógica personalizada como construir tu propio 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(); // Eliminar el último separator return result; }
import { Children } from 'react'; export default function RowList({ children }) { return ( <div className="RowList"> <h1 className="RowListHeader"> Filas totales: {Children.count(children)} </h1> {Children.map(children, child => <div className="Row"> {child} </div> )} </div> ); }
Convertir children a un array
Llama Children.toArray(children)
para convertir la estructura de datos children
en un array de JavaScript regular. Esto te permite manipular el array con métodos de array integrados como filter
, sort
, o reverse
.
import { Children } from 'react'; export default function ReversedList({ children }) { const result = Children.toArray(children); result.reverse(); return result; }
Alternativas
Exponer varios componentes
Manipular hijos con los métodos de Children
a menudo conduce a un código frágil. Cuando pasas hijos a un componente en JSX, por lo general, no esperas que el componente manipule o transforme los hijos individuales.
Cuando puedas, trata de evitar el uso de los métodos de Children
. Por ejemplo, si quieres que cada hijo de RowList
esté envuelto en <div className="Row">
, exporta un componente Row
y envuelve manualmente cada fila dentro de él de esta manera:
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList> <Row> <p>Este es el primer elemento.</p> </Row> <Row> <p>Este es el segundo elemento.</p> </Row> <Row> <p>Este es el tercer elemento.</p> </Row> </RowList> ); }
A diferencia de usar Children.map
, este enfoque no envuelve a todos los hijos automáticamente. Sin embargo, este enfoque tiene un beneficio significativo en comparación con el ejemplo anterior con Children.map
porque funciona incluso si sigues extrayendo más componentes. Por ejemplo, todavía funciona si extraes tu propio componente MoreRows
:
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList> <Row> <p>Este es el primer elemento.</p> </Row> <MoreRows /> </RowList> ); } function MoreRows() { return ( <> <Row> <p>Este es el segundo elemento.</p> </Row> <Row> <p>Este es el tercer elemento.</p> </Row> </> ); }
Esto no funcionaría con Children.map
porque «vería» <MoreRows />
como un solo hijo (y una sola fila).
Aceptar un array de objetos como prop
También puedes pasar explícitamente un array como prop. Por ejemplo, este RowList
acepta el array rows
como una prop:
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList rows={[ { id: 'first', content: <p>Este es el primer elemento.</p> }, { id: 'second', content: <p>Este es el segundo elemento.</p> }, { id: 'third', content: <p>Este es el tercer elemento.</p> } ]} /> ); }
Ya que rows
es un array regular de JavaScript, el componente RowList
puede usar métodos de incorporados de arrays como map
en él.
Este patrón es especialmente útil cuando deseas poder pasar más información como datos estructurados junto con los hijos. En el siguiente ejemplo, el componente TabSwitcher
recibe un array de objetos tabs
como prop:
import TabSwitcher from './TabSwitcher.js'; export default function App() { return ( <TabSwitcher tabs={[ { id: 'first', header: 'First', content: <p>Este es el primer elemento.</p> }, { id: 'second', header: 'Second', content: <p>Este es el segundo elemento.</p> }, { id: 'third', header: 'Third', content: <p>Este es el tercer elemento.</p> } ]} /> ); }
A diferencia de pasar los hijos como JSX, este enfoque te permite asociar algunos datos adicionales como header
con cada elemento. Como estás trabajando con tabs
directamente, y es un array, no necesita los métodos de Children
.
Llamar a una prop de renderizado para adaptar el renderizado
En lugar de producir JSX para cada elemento, también puedes pasar una función que devuelva JSX, y llamar a esa función cuando sea necesario. En este ejemplo, el componente App
pasa una función renderContent
al componente TabSwitcher
. El componente TabSwitcher
llama a renderContent
solo para la pestaña (tab) seleccionada:
import TabSwitcher from './TabSwitcher.js'; export default function App() { return ( <TabSwitcher tabIds={['primero', 'segundo', 'tercero']} getHeader={tabId => { return tabId[0].toUpperCase() + tabId.slice(1); }} renderContent={tabId => { return <p>Este es el elemento {tabId}.</p>; }} /> ); }
Una prop como renderContent
se llama render prop o prop de renderizado, porque es un prop que especifica cómo representar una parte de la interfaz de usuario. Sin embargo, no tiene nada de especial: es una prop regular que resulta ser una función.
Las props de renderizado son funciones, por lo que les puedes pasar información. Por ejemplo, este componente RowList
pasa el id
y el index
de cada fila a la prop de renderizado renderRow
, que usa index
para resaltar las filas pares:
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList rowIds={['primero', 'segundo', 'tercero']} renderRow={(id, index) => { return ( <Row isHighlighted={index % 2 === 0}> <p>Este es el elemento {id}.</p> </Row> ); }} /> ); }
Este es otro ejemplo de cómo los componentes padre e hijo pueden cooperar sin manipular a los hijos.
Solución de problemas
Paso un componente personalizado, pero los métodos de Children
no muestran su resultado del renderizado
Supongamos que pasa dos hijos a RowList
como esto:
<RowList>
<p>First item</p>
<MoreRows />
</RowList>
Si haces Children.count(children)
dentro de RowList
, obtendrás 2
. Incluso si MoreRows
renderiza 10 elementos diferentes, o si devuelve null
, Children.count(children)
seguirá siendo 2
. Desde la perspectiva de RowList
, solo «ve» el JSX que ha recibido. No «ve» las partes internas del componente MoreRows
.
La limitación dificulta la extracción de un componente. Por eso las alternativas son preferibles al uso de Children
.