Fermer

août 4, 2021

React Children et méthodes d'itération —


Résumé rapide ↬

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.toArrayque 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 :

    1. Nous utilisons la méthode React.Children.toArray pour nous assurer que la prop children est toujours un tableau. Si nous ne le faisons pas, faire children.length peut exploser car la prop children peut être un objet, un tableau ou même une fonction. De plus, si nous essayons d'utiliser la méthode array .map sur children directement, cela pourrait exploser.
    2. Dans le composant parent Breadcrumbs nous parcourons ses enfants en utilisant la méthode utilitaire React.Children.map.
    3. Parce que nous avons accès à index à l'intérieur de la fonction itérateur (deuxième argument de la fonction de rappel de React. 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.
    4. 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 Breadcrumbsil 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.toArrayrafraîchissons notre mémoire sur l'un d'entre eux : React.Children.toArray.

    Plus après le saut ! Continuez à lire ci-dessous ↓

    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 Debuggerqui 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 les enfants 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 des enfants avant de les transmettre.

    Décomposons cela :

    1. Renvoie le children structure de données opaque sous forme de tableau plat.
    2. 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 que toArray 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 enfantsil 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 childrenont 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 childrencomme 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.

    1. S'il y a un besoin absolu que children doit toujours être un tableau, vous pouvez utiliser React.Children.toArray(children) à la place. Cela fonctionnera parfaitement même lorsque children est également un objet ou une fonction.

    2. Si vous devez trier, filtrer ou découper children prop vous pouvez compter sur React.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 :

    1. C'est une fonction qui prend children comme seul argument nécessaire.
    2. Itère sur React.Children.toArray(children) et rassemble les enfants dans un tableau d'accumulateurs.
    3. 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.
    4. 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.
    5. 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.
    6. 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. »

    Dan Abramov

    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 :

    1. 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, et React.Children.toArray en profondeur.
    2. Nous avons vu comment React.Children.toArray convertit la prop opaque children — 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…
    3. Nous avons appris que React.Children.toArray ne traverse pas les fragments React.
    4. Nous avons appris l'existence d'un package open source appelé react-keyed-flatten -children et compris comment cela résout le problème.
    5. 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

    Smashing Editorial(ks, vf, yk, il)






    Source link