Children
Children
te permite manipular y transformar el JSX que recibes como el children
prop.
const mappedChildren = Children.map(children, child =>
<div className="Row">
{child}
</div>
);
Uso
Transformar children
Para transformar el JSX de tu componente que recibe como el children
prop, llama 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, el RowList
envuelve cada child que recibe un <div className="Row>
contenedor. Por ejemplo, digamos que el componente padre pasa tres etiquetas <p>
como el children
prop 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 del 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 el children
se considera la estructura de datos opaque. Esto significa que incluso si a veces es un array, no debe 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 transformarlo.
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
Porqué el children prop no siempre es un array?
Porqué el children prop no siempre es un array?
En React, el children
prop es considerado una estructura de datos opaque. Esto significa que no debe confiar en cómo está estructurado. 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 child, entonces React no creará un array extra ya que esto conduciría a una sobrecarga de memoria innecesaria. Siempre y cuando use los métodos Children
en lugar de hacer una introspección directa de los children
prop, 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 has pasado a ella. Esto asegura que los JSX children originales no “pierdan” las keys incluso si se envuelven como en el ejemplo anterior.
Corriendo un código para cada child
Llama Children.forEach
para iterar sobre cada child en la estructura de datos children
. No devuelve ningún valor y es similar al método array forEach
. Puede usarlo para ejecutar una lógica personalizada como construir su 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(); // Remueve el ultimo 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 le permite manipular el array con métodos de array integrados como filter
, sort
, ó reverse
.
import { Children } from 'react'; export default function ReversedList({ children }) { const result = Children.toArray(children); result.reverse(); return result; }
Alternativas
Exponer varios componentes
Manipular children con los métodos Children
a menudo conduce a un código frágil. Cuando tú pasas un children a un componente en JSX, por lo general, no espera que el componente manipule o transforme al children individualmente.
Cuando pueda, trate de evitar el uso de los métodos Children
. Por ejemplo, si quieres que cada child de RowList
este envuelto en <div className="Row">
, exporte un componente Row
y envuelve manualmente cada Row 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 child 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 extrae su 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 child (y un solo row).
Aceptar un array de objetos como prop
También puede pasar explícitamente un array como prop. Por ejemplo, este RowList
acepta el array rows
como un 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 matriz incorporados como map
en el.
Este patrón es especialmente útil cuando desea poder pasar más información como datos estructurados junto con un children. 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 el children como JSX, este enfoque le permite asociar algunos datos adicionales como header
con cada elemento. Porque estás trabajando con las tabs
directamente, y es una matriz, no necesita los métodos Children
.
Llamar a un accesorio de representación para personalizar la representación
En lugar de producir JSX para cada artículo, también puede pasar una función que devuelve 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 renderContent
solo para el tab seleccionado:
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 {tabId} elemento.</p>; }} /> ); }
Un prop como renderContent
se llama como render prop. Porque es un prop que especifica cómo representar una parte de la interfaz de usuario. Sin embargo, no tiene nada de especial: es un prop regular que resulta ser una función.
Render props son funciones, por lo que les puedes pasar información. Por ejemplo, este componente RowList
pasa el id
y el index
por cada fila del render prop 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 {id} elemento.</p> </Row> ); }} /> ); }
Este es otro ejemplo de cómo los componentes padre e hijo pueden cooperar sin manipular a los children.
Referencia
Children.count(children)
Llama Children.count(children)
para contar el numero children 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 dechildren
prop recibido por tú componente.
Regresa
El numero de nodos de estos children
.
Advertencias
- Nodos vacíos (
null
,undefined
, y Booleans), strings, numbers, y React elements cuentan como nodos individuales. Arrays no cuentan como arreglos individuales, pero sus children si. El recorrido no va más profundo que React elements: ellos no se renderizan, y sus children no son afectados. Fragments no son afectados.
Children.forEach(children, fn, thisArg?)
Llamar Children.forEach(children, fn, thisArg?)
para correr algún código por cada child 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 dechildren
prop recibido por tú componente.fn
: La función que desea ejecutar para cada child, similar al callback del método arrayforEach
. Se llamará con el child como primer argumento y su index como segundo argumento. el index 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
.
Regresa
Children.forEach
regresa undefined
.
Advertencias
- Nodos vacíos (
null
,undefined
, y Booleans), strings, numbers, y React elements cuentan como nodos individuales. Arrays no cuentan como arreglos individuales, pero sus children si. El recorrido no va más profundo que React elements: ellos no se renderizan, y sus children no son afectados. Fragments no son afectados.
Children.map(children, fn, thisArg?)
Llamar Children.map(children, fn, thisArg?)
para mapear o transformar cada child 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 dechildren
prop recibido por tú componente.fn
: La función de mapeo, similar al callback del método arraymap
. Se llamará con el child como primer argumento y su index como segundo argumento. el index empieza en0
y se incrementa por cada llamada. Necesitas regresar un React node de esta función. Esto puede ser un nodo vacío (null
,undefined
, o un Boolean), un string, un number, un React element, o un array de otros React nodes.- opcional
thisArg
: El valorthis
con el que se debe llamar a la funciónfn
. Si se omite, esundefined
.
Regresa
Si children
es null
o undefined
, regresa el mismo valor.
De lo contrario, devuelve un array plano que consta de los nodos que ha devuelto de la función fn
. devuelta contendrá todos los nodos que devolvió excepto por null
y undefined
.
Advertencias
-
Nodos vacíos (
null
,undefined
, y Booleans), strings, numbers, y React elements cuentan como nodos individuales. Arrays no cuentan como arreglos individuales, pero sus children si. El recorrido no va más profundo que React elements: ellos no se renderizan, y sus children no son afectados. Fragments no son afectados. -
Si devuelve un elemento o un array de elementos con keys de
fn
, las keys de los elementos devueltos se combinará automáticamente con la clave del elemento original correspondiente dechildren
. Cuando devuelves múltiples elementos defn
en una array, sus keys solo necesitan ser únicas localmente entre sí.
Children.only(children)
Llamar Children.only(children)
para afirmar que children
representa un solo React element.
function Box({ children }) {
const element = Children.only(children);
// ...
Parámetros
children
: El valor dechildren
prop recibido por tú componente.
Regresa
Si children
es un elemento valido, regresa ese elemento.
De lo contrario, lanza un error.
Advertencias
- Este método siempre se lanza si pasas un array (como el valor de retorno de
Children.map
) comochildren
. En otras palabras, hace cumplir quechildren
es un solo React element, no es que sea un array con un solo elemento.
Children.toArray(children)
Llamar 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 dechildren
prop recibido por tú componente.
Regresa
Devuelve un array plano de elementos en children
.
Advertencias
- Nodos vacíos (
null
,undefined
, y Booleans) 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.
Solución de problemas
Paso un componente personalizado, pero los métodos Children
y no muestran su resultado de renderizado
Supongamos que pasa dos children 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 vuelve 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
.