Dans cet article, nous discuterons et découvrirons le cas d'utilisation de l'itération sur React children
et les moyens de le faire. En particulier, nous approfondirons l'une des méthodes utilitaires, React.Children.toArray
que React nous donne, qui aide à itérer sur les enfants
d'une manière qui garantit les performances et le déterminisme.
L'accessoire le plus évident et le plus courant avec lequel les développeurs travaillent dans React est l'accessoire children
. Dans la majorité des cas, il n'est pas nécessaire de comprendre à quoi ressemble l'accessoire children
. Mais dans certains cas, nous voulons inspecter la prop children
pour peut-être envelopper chaque enfant dans un autre élément/composant ou pour les réorganiser ou les découper. Dans ces cas, inspecter l'apparence de l'accessoire children
devient essentiel.
Dans cet article, nous examinerons un utilitaire React React.Children.toArray
qui nous permet de préparer le children
prop pour l'inspection et l'itération, certaines de ses lacunes et comment les surmonter – grâce à un petit package open source, pour garder notre fonction de code React comme elle est censée se comporter de manière déterministe, en gardant les performances intactes. Si vous connaissez les bases de React et avez au moins une idée de ce qu'est l'accessoire children
dans React, cet article est pour vous.
Lorsque vous travaillez avec React, la plupart du temps, nous ne touchons pas la prop children
pas plus que de l'utiliser directement dans les composants React.
function Parent({ children }) {
retour {enfants} ;
}
Mais parfois, nous devons itérer sur la prop children
afin de pouvoir améliorer ou modifier les enfants sans que l'utilisateur des composants le fasse explicitement lui-même. Un cas d'utilisation courant consiste à transmettre les informations relatives à l'index d'itération aux composants enfants d'un parent comme suit :
import { Children, cloneElement } from "react" ;
fonction Breadcrumbs({ enfants }) {
const arrayChildren = Children.toArray(children);
revenir (
{Children.map(arrayChildren, (enfant, index) => {
const isLast = index === arrayChildren.length - 1;
if (! isLast && ! child.props.link ) {
lancer une nouvelle erreur (
`BreadcrumbItem enfant no. ${index + 1}
devrait être passé un 'lien' prop`
)
}
revenir (
<>
{enfant.props.link ? (
{cloneElement(enfant, {
estDernier,
})}
) : (
{cloneElement(enfant, {
estDernier,
})}
)}
{!estDernier && (
>
)}
>
);
})}
);
}
function BreadcrumbItem({ isLast, children }) {
revenir (
{enfants}
);
}
exporter la fonction par défaut App() {
revenir (
Goibibo
Hôtels
Un nom d'hôtel de fantaisie
);
}
Ici, nous procédons comme suit :
- Nous utilisons la méthode
React.Children.toArray
pour nous assurer que la propchildren
est toujours un tableau. Si nous ne le faisons pas, fairechildren.length
peut exploser car la propchildren
peut être un objet, un tableau ou même une fonction. De plus, si nous essayons d'utiliser la méthode array.map
surchildren
directement, cela pourrait exploser. - Dans le composant parent
Breadcrumbs
nous parcourons ses enfants en utilisant la méthode utilitaireReact.Children.map
. - Parce que nous avons accès à
index
à l'intérieur de la fonction itérateur (deuxième argument de la fonction de rappel deReact. Children.map
), nous sommes en mesure de détecter si l'enfant est le dernier enfant ou non. que l'enfant peut se styliser en fonction de celui-ci. - S'il ne s'agit pas du dernier enfant, nous nous assurons que tous les enfants qui ne sont pas le dernier ont un accessoire
link
en lançant une erreur s'ils ne le font pas. Nous clonons l'élément comme nous l'avons fait à l'étape 4. et transmettons la propriétéisLast
comme nous l'avons fait auparavant, mais nous enveloppons également cet élément cloné dans une balise d'ancrage.
L'utilisateur de Breadcrumbs
et BreadcrumbItem
n'ont pas à se soucier des enfants qui doivent avoir des liens et de la façon dont ils doivent être stylisés. À l'intérieur du composant Breadcrumbs
il est automatiquement géré.
Ce modèle de implicitement passant des accessoires et/ou ayant state
dans le parent et passant l'état et les changeurs d'état jusqu'aux enfants en tant qu'accessoires sont appelés le modèle de composant composé. Vous connaissez peut-être ce modèle du composant Switch
de React Router, qui prend les composants Route
comme ses enfants :
// exemple de la documentation du routeur React
// https://reactrouter.com/web/api/Switch
importer { Route, Switch } de "react-router" ;
laisser routes = (
);
Maintenant que nous avons établi qu'il existe des besoins où nous devons parfois itérer sur children
prop, et après avoir utilisé deux des méthodes utilitaires enfants React.Children.map
et React.Children.toArray
rafraîchissons notre mémoire sur l'un d'entre eux : React.Children.toArray
.
React.Children.toArray
Let's commencez par voir avec un exemple ce que fait cette méthode et où elle peut être utile.
import { Children } from 'react'
fonction Débogueur({enfants}) {
// notons certaines choses
console.log(enfants);
console.log(
Enfants.àArray(enfants)
)
rendre les enfants ;
}
fruits const = [
{name: "apple", id: 1},
{name: "orange", id: 2},
{name: "mango", id: 3}
]
exporter la fonction par défaut App() {
revenir (
Astuces CSS
Magazine fracassant
{
fruits.map(fruit => {
revenir (
{fruit.nom}
)
})
}
)
}
Nous avons un composant Debugger
qui ne fait pas grand-chose en termes de rendu — il renvoie simplement children
tel quel. Mais il enregistre deux valeurs : children
et React.Children.toArray(children)
.
Si vous ouvrez la console, vous pourrez voir la différence.
- La première instruction qui enregistre la prop
children
affiche ce qui suit comme structure de données de sa valeur :
[
Object1, ----> first anchor tag
Object2, ----> second anchor tag
[
Object3, ----> first fruit
Object4, ----> second fruit
Object5] ----> troisième fruit
]
]
- La deuxième instruction qui enregistre
React.Children.toArray(children)
enregistre :
[
Object1, ----> first anchor tag
Object2, ----> second anchor tag
Object3, ----> first fruit
Object4, ----> second fruit
Object5, ----> third fruit
]
Lisons la documentation de la méthode dans la documentation de React pour comprendre ce qui se passe.
React.Children.toArray
renvoie lesenfants
structure de données opaque sous forme de tableau plat avec des clés affectées à chaque enfant. Utile si vous souhaitez manipuler des collections d'enfants dans vos méthodes de rendu, en particulier si vous souhaitez réorganiser ou découper desenfants
avant de les transmettre.
Décomposons cela :
- Renvoie le
children
structure de données opaque sous forme de tableau plat. - Avec des clés affectées à chaque enfant.
Le premier point indique que children
(qui est une structure de données opaque, ce qui signifie qu'elle peut être un objet, un tableau ou une fonction, comme décrit précédemment) est converti en un tableau plat. Tout comme nous l'avons vu dans l'exemple ci-dessus. De plus, ce commentaire sur le problème GitHub explique également son comportement :
Il (
React.Children.toArray
) ne retire pas les enfants des éléments et ne les aplatit pas, cela ne serait pas vraiment ne veut rien dire. Il aplatit les tableaux et objets imbriqués, c'est-à-dire de sorte que[['a', 'b'],['c', ['d']]]
devienne quelque chose de similaire à['a', 'b', 'c', 'd']
.
React.Children.toArray(
[
["a", "b"],
["c", ["d"]]
]
).length === 4;
Voyons ce que dit le deuxième point ("Avec des clés affectées à chaque enfant."), en développant un enfant chacun à partir des journaux précédents de l'exemple.
Expanded Child From console.log(enfants)
{
$$typeof : Symbole(react.element),
clé : nulle,
accessoires : {
href : "https://smashingmagazine.com",
enfants : "Magazine Smashing",
style : {padding : "0 10px"}
},
réf : nul,
type A",
// … autres propriétés
}
Enfant étendu de console.log(React.Children.toArray(children))
{
$$typeof : Symbole(react.element),
clé : ".0",
accessoires : {
href : "https://smashingmagazine.com",
enfants : "Magazine Smashing",
style : {padding : "0 10px"}
},
réf : nul,
type A",
// … autres propriétés
}
Comme vous pouvez le voir, en plus d'aplatir la propriété children
dans un tableau plat, il ajoute également des clés uniques à chacun de ses enfants. À partir de la documentation React :
React.Children.toArray()
modifie les clés pour préserver la sémantique des tableaux imbriqués lors de l'aplatissement des listes d'enfants. C'est-à-dire quetoArray
préfixe chaque clé dans le tableau renvoyé afin que la clé de chaque élément soit limitée au tableau d'entrée le contenant.
Parce que la méthode .toArray
peut changer l'ordre et place des enfants
il doit s'assurer qu'il conserve des clés uniques pour chacun d'eux pour la réconciliation et l'optimisation du rendu.
Attardons un peu plus sur de sorte que la clé de chaque élément est limitée au tableau d'entrée qui le contient.
en examinant les clés de chaque élément du deuxième tableau (correspondant à console.log(React.Children.toArray(children))
).
import { Enfants } de 'react'
fonction Débogueur({enfants}) {
// notons certaines choses
console.log(enfants);
console.log(
Children.map(Children.toArray(children), child => {
retourner la clé.enfant
}).join('n')
)
rendre les enfants ;
}
fruits const = [
{name: "apple", id: 1},
{name: "orange", id: 2},
{name: "mango", id: 3}
]
exporter la fonction par défaut App() {
revenir (
Astuces CSS
Magazine fracassant
{
fruits.map(fruit => {
revenir (
{fruit.nom}
)
})
}
)
}
.0 ----> premier lien
.1 ----> deuxième lien
.2:0 ----> premier fruit
.2:1 ----> deuxième fruit
.2:2 ----> troisième fruit
Comme vous pouvez le voir, les fruits, qui étaient à l'origine un tableau imbriqué dans le tableau d'origine children
ont des clés préfixées par . 2
. Le .2
correspond au fait qu'ils faisaient partie d'un tableau. Les suffixes, à savoir :0
,:1
:2
correspondent aux clés par défaut des éléments React (fruits). Par défaut, React utilise l'index comme clé, si aucune clé n'est spécifiée pour les éléments d'une liste.
Supposons donc que vous ayez trois niveaux d'imbrication dans le tableau children
comme ceci :
importer { Enfants } de 'react'
fonction Débogueur({enfants}) {
const retVal = Children.toArray(children)
console.log(
Enfants.map(retVal, enfant => {
retourner la clé.enfant
}).join('n')
)
retourner retVal
}
exporter la fonction par défaut App() {
const arrayOfReactElements = [
Premier,
[
Deuxième,
[
Troisième
]
]
] ;
revenir (
{arrayOfReactElements}
)
}
Les clés ressembleront à
.1 $
.1 : 2 $
.1:1:$3
Les suffixes $1
$2
$3
sont dus aux clés d'origine placées sur les éléments React dans un tableau, sinon React se plaint de manque de clés 😉 .
De tout ce que nous avons lu jusqu'à présent, nous pouvons arriver à deux cas d'utilisation pour React.Children.toArray
.
-
S'il y a un besoin absolu que
children
doit toujours être un tableau, vous pouvez utiliserReact.Children.toArray(children)
à la place. Cela fonctionnera parfaitement même lorsquechildren
est également un objet ou une fonction. -
Si vous devez trier, filtrer ou découper
children
prop vous pouvez compter surReact.Children.toArray
pour toujours conserver les clés uniques de tous les enfants.
Il y a un problème avec React.Children.toArray
🤔. Regardons ce morceau de code pour comprendre quel est le problème :
import { Children } from 'react'
fonction Liste({enfants}) {
revenir (
{
Enfants.versTableau(
enfants
).map((enfant, index) => {
revenir (
-
{enfant}
)
})
}
)
}
exporter la fonction par défaut App() {
revenir (
Google
<>
Magazine fracassant
{"Site Web d'Arihant"}
>
)
}
Si vous voyez ce qui est rendu pour les enfants du fragment, vous verrez que les deux liens sont rendus dans une balise li
! 😱
C'est parce que React.Children.toArray
ne traverse pas en fragments. Alors, que pouvons-nous faire à ce sujet? Heureusement, rien 😅 . Nous avons déjà un package open source appelé react-keyed-flatten-children
. C'est une petite fonction qui fait sa magie.
Voyons ce qu'elle fait. En pseudo-code (ces points sont liés dans le code réel ci-dessous), il fait ceci :
- C'est une fonction qui prend
children
comme seul argument nécessaire. - Itère sur
React.Children.toArray(children)
et rassemble les enfants dans un tableau d'accumulateurs. - Lors de l'itération, si un nœud enfant est une chaîne ou un nombre, il pousse la valeur telle qu'elle est dans le tableau d'accumulateurs.
- Si le nœud enfant est un élément React valide, il le clone, lui donne la clé appropriée et le pousse vers le tableau d'accumulateurs.
- Si le nœud enfant est un fragment, la fonction s'appelle avec les enfants du fragment comme argument. (c'est ainsi qu'il parcourt un fragment ) et pousse le résultat de l'appel lui-même dans le tableau d'accumulateurs.
- Tout en faisant tout cela, il garde la trace de la profondeur de la traversée (des fragments), donc que les enfants à l'intérieur des fragments auraient des clés correctes, de la même manière que les clés fonctionnent avec un tableau imbriqué s, comme nous l'avons vu plus haut.
import {
Enfants,
isValidElement,
cloneElement
} de "réagir" ;
import { isFragment } de "react-is" ;
type d'importation {
ReactNode,
ReactChild,
} de 'réagir'
/*************** 1. ***************/
exporter la fonction par défaut flattenChildren(
// seul argument nécessaire
enfants : ReactNode,
// utilisé uniquement pour le débogage
profondeur : nombre = 0,
// n'est pas obligatoire, commencez par défaut = []
clés : (chaîne | nombre)[] = []
): ReactChild[] {
/****************** 2. *******************/
return Children.toArray(children).reduce(
(acc: ReactChild[]node, nodeIndex) => {
if (isFragment(noeud)) {
/****************** 5. *******************/
acc.push.apply(
acc,
aplatirEnfants(
node.props.children,
profondeur + 1,
/****************** 6. *******************/
keys.concat(node.key || nodeIndex)
)
);
} autre {
/****************** 4. *******************/
if (isValidElement(node)) {
acc.push(
cloneElement(noeud, {
/****************** 6. *******************/
clé : keys.concat(String(node.key)).join('.')
})
);
} sinon si (
/****************** 3. *******************/
type de nœud === "chaîne"
|| type de nœud === "nombre"
) {
acc.push(nœud);
}
}
retour acc;
},
/****************** Acculumator Array ***************/
[]
);
}
Réessayez notre exemple précédent pour utiliser cette fonction et voyons par nous-mêmes que cela résout notre problème.
import flattenChildren from 'react-keyed-flatten-children'
importer { Fragment } depuis 'react'
fonction Liste({enfants}) {
revenir (
{
aplatirEnfants(
enfants
).map((enfant, index) => {
retour - {enfant}
})
}
)
}
exporter la fonction par défaut App() {
revenir (
Google
Magazine fracassant
{"Site Web d'Arihant"}
)
}
Woooheeee ! Cela fonctionne.
En tant que module complémentaire, si vous débutez dans les tests – comme je le suis au moment d'écrire ces lignes – vous pourriez être intéressé par 7 tests écrits pour cette fonction utilitaire. Ce sera amusant de lire les tests pour en déduire la fonctionnalité de la fonction. , et est en mode maintenance. »
Le problème avec l'utilisation des méthodes Children
pour modifier le comportement des enfants
est que ils ne fonctionnent que pour un niveau d'imbrication de composants. Si nous enveloppons l'un de nos enfants
dans un autre composant, nous perdons la composabilité. Voyons ce que je veux dire par là, en reprenant le premier exemple que nous avons vu — le fil d'Ariane.
import { Children, cloneElement } from "react";
fonction Breadcrumbs({ enfants }) {
revenir (
{Children.map(children, (enfant, index) => {
const isLast = index === children.length - 1;
// si (! isLast && ! child.props.link ) {
// lancer une nouvelle erreur (`
// BreadcrumbItem enfant no.
// ${index + 1} doit recevoir une prop 'link'`
// )
// }
revenir (
<>
{enfant.props.link ? (
{cloneElement(enfant, {
estDernier,
})}
) : (
{cloneElement(enfant, {
estDernier,
})}
)}
{!estDernier && (
>
)}
>
);
})}
);
}
function BreadcrumbItem({ isLast, children }) {
revenir (
{enfants}
);
}
const BreadcrumbItemCreator = () =>
Magazine fracassant
exporter la fonction par défaut App() {
revenir (
Goibibo
Hôtels Goibibo
Un nom d'hôtel de fantaisie
);
}
Bien que notre nouveau composant ait été rendu, notre composant Breadcrumb
n'a aucun moyen d'en extraire la prop link
à cause de quoi, il ne rend pas comme lien. explique ce problème en détail et comment react-call-return
l'a résolu. Étant donné que le package n'a jamais été publié dans aucune version de React, il est prévu de s'en inspirer et de préparer quelque chose pour la production.
Conclusion
Pour conclure, nous avons appris :
- Le
]Méthodes utilitaires React.Children
. Nous en avons vu deux :React.Children.map
pour voir comment l'utiliser pour créer des composants composés, etReact.Children.toArray
en profondeur. - Nous avons vu comment
React.Children.toArray
convertit la prop opaquechildren
— qui peut être un objet, un tableau ou une fonction — en un tableau plat, de sorte que l'on puisse opérer dessus de la manière requise — trier, filtrer , splice, etc… - Nous avons appris que
React.Children.toArray
ne traverse pas les fragments React. - Nous avons appris l'existence d'un package open source appelé
react-keyed-flatten -children
et compris comment cela résout le problème. - Nous avons vu que les utilitaires
Children
sont en mode maintenance parce qu'ils ne se composent pas bien.
Vous pouvez également être intéressé à lire comment utiliser d'autres méthodes Children
pour faire tout ce que vous pouvez faire avec children
dans Max S article de blog de toiber React Children Deep Dive.
Ressources
Source link