Écriture d'une API pour les composants React, partie 3: l'ordre des accessoires est important

Écriture d'une API pour les composants React, partie 1: ne créez pas d'accessoires conflictuels

Écriture d'une API pour les composants React, partie 2: donner des noms au comportement, pas à l'interaction

Écriture d'une API pour les composants React, partie 3: l'ordre des accessoires est important

Écriture d'une API pour React Components, Partie 4: Méfiez-vous de l'Apropacalypse!

Écriture d'une API pour les composants React, partie 5: il suffit d'utiliser la composition

Nous écrivons API pour les composants React, partie 6: nous créons la communication entre les composants

Commençons par un simple composant React qui affiche une balise d'ancrage:


lien


<Link href="sid.studio">Click me</Link> //   : <a href="sid.studio" class="link">Click me</a> 

Voici à quoi ressemble le code du composant:


 const Link = props => { return ( <a href={props.href} className="link"> {props.children} </a> ) } 

Nous voulons également pouvoir ajouter des attributs html à l'élément, tels que id , target , title , data-attr , etc.


Comme il existe de nombreux attributs HTML, nous pouvons simplement passer tous les accessoires et ajouter ceux dont nous avons besoin (comme className )


(Remarque: vous ne devez pas transmettre les attributs que vous avez créés pour ce composant qui ne figurent pas dans la spécification HTML)


Dans ce cas, vous pouvez simplement utiliser className


 const Link = props => { /*    (spread ),     ( ) */ return <a {...props} className="link" /> } 

C'est là que ça devient intéressant.


Tout semble aller bien quand quelqu'un passe un id ou une target :


 <Link href="sid.studio" id="my-link">Click me</Link> //   : <a href="sid.studio" id="my-link" class="link">Click me</a> 

mais que se passe-t-il lorsque quelqu'un passe className ?


lien


 <Link href="sid.studio" className="red-link">Click me</Link> //   : <a href="sid.studio" class="link">Click me</a> 

Eh bien, il ne s'est rien passé. React a complètement ignoré la classe personnalisée. Revenons à la fonction:


 const Link = props => { return <a {...props} className="link" /> } 

Ok, imaginons comment celui-ci ...props compile, le code ci-dessus est équivalent à ceci:


 const Link = props => { return ( <a href="sid.studio" className="red-link" className="link" > Click me </a> ) } 

Vous voyez le conflit? Il existe deux className . Comment React gère-t-il cela?


Eh bien, React ne fait rien. Babel le fait!


N'oubliez pas que JSX "produit" React.createElement . Les accessoires sont convertis en objet et passés en argument. Les objets ne prennent pas en charge les clés en double, donc le deuxième nom de className remplacera le premier.


 const Link = props => { return React.createElement( 'a', { className: 'link', href: 'sid.studio' }, 'Click me' ) } 



Bon, maintenant que nous connaissons le problème, comment pouvons-nous le résoudre?


Il est utile de comprendre que cette erreur s'est produite en raison d'un conflit de nom, et cela peut se produire avec n'importe quel accessoire, et pas seulement avec className . La décision dépend donc du comportement que vous souhaitez mettre en œuvre.


Il existe trois scénarios possibles:


  1. Un développeur utilisant notre composant devrait pouvoir remplacer la valeur de prop par défaut.
  2. Nous ne voulons pas laisser le développeur changer certains accessoires
  3. Le développeur doit pouvoir ajouter des valeurs tout en conservant la valeur par défaut.

Démontons-les un par un.


1. Le développeur utilisant notre composant devrait être en mesure de remplacer la valeur de prop par défaut


C'est le comportement que vous attendez habituellement des autres attributs - id , title , etc.


Nous voyons souvent le paramètre d' test id dans le cosmos (le système de conception sur lequel je travaille). Chaque composant reçoit un data-test-id par défaut, parfois les développeurs souhaitent ajouter leur propre identifiant de test à la place, pour indiquer une utilisation spécifique.


Voici un tel cas d'utilisation:


chapelure


 const Breadcrumb = () => ( <div className="breadcrumb" data-test-id="breadcrumb"> <Link data-test-id="breadcrumb.link">Home</Link> <Link data-test-id="breadcrumb.link">Parent</Link> <Link data-test-id="breadcrumb.link">Page</Link> </div> ) 

Breadcrumb utilise le lien, mais vous voulez pouvoir l'utiliser dans des tests avec un data-test-id plus spécifique. Il y a un défaut.


Dans la plupart des cas, les accessoires personnalisés doivent avoir la priorité sur les accessoires par défaut.


En pratique, cela signifie que les accessoires par défaut doivent être utilisés en premier, puis {...props} pour les remplacer.


 const Link = props => { return ( <a className="link" data-test-id="link" {...props} /> ) } 

N'oubliez pas que la deuxième apparence de data-test-id (provenant d'accessoires) remplacera la première (par défaut). Par conséquent, lorsqu'un développeur attache son propre data-test-id ou className , il remplace celui qui était par défaut:


 1. <Link href="sid.studio">Click me</Link> 2. <Link href="sid.studio" data-test-id="breadcrumb.link">Click me</Link> //   : 1. <a class="link" href="sid.studio" data-test-id="link">Click me</a> 2. <a class="link" href="sid.studio" data-test-id="breadcrumb.link">Click me</a> 

Nous pouvons faire de même avec className :


lien rouge


 <Link href="sid.studio" className="red-link">Click me</Link> //   : <a href="sid.studio" class="red-link" data-test-id="link">Click me</a> 

Cela semble plutôt étrange, je ne suis pas sûr que nous devrions permettre cela! Parlons un peu plus de cela.


2. Nous ne voulons pas autoriser le développeur à modifier certains accessoires


Supposons que nous ne voulons pas que les développeurs changent l'apparence (via className ), mais cela ne nous dérange pas qu'ils changent d'autres accessoires, tels que id , data-test-id , etc.


Nous pouvons le faire en ordonnant l'ordre de nos attributs:


 const Link = props => { return ( <a data-test-id="link" {...props} className="link" /> ) } 

N'oubliez pas que l'attribut de droite remplacera l'attribut de gauche. Ainsi, tout ce qui précède {...props} peut être redéfini, mais tout ce qui se trouve après ne peut pas être redéfini.


Pour simplifier le travail des développeurs, vous pouvez afficher un avertissement indiquant que vous ne pouvez pas spécifier votre nom de className .


J'aime créer mes propres accessoires de vérification de type pour cela:


 Link.PropTypes = { className: function(props) { if (props.className) { return new Error( `  className  Link,      ` ) } } } 

J'ai une vidéo qui parle de vérifier les types d'accessoires personnalisés , au cas où vous seriez intéressé à les écrire.


Désormais, lorsque le développeur essaie de remplacer className , cela ne fonctionnera pas et le développeur recevra un avertissement.


lien


 <Link href="sid.studio" className="red-link">Click me</Link> //   : <a href="sid.studio" class="link">Click me</a> 

 :   :   className  Link,       

Honnêtement, je n'ai dû utiliser ce modèle qu'une ou deux fois. Habituellement, vous faites confiance au développeur qui utilise votre composant.


Ce qui nous amène au partage.


3. Le développeur doit pouvoir ajouter des valeurs tout en conservant la valeur par défaut


Il s'agit peut-être du cas d'utilisation le plus courant pour les classes.


lien


 <Link href="sid.studio" className="underline">Click me</Link> //   : <a href="sid.studio" class="link underline">Click me</a> 

L'implémentation ressemble à ceci:


 const Link = props => { /*  className   */ const { className, otherProps } = props /*     */ const classes = 'link ' + className return ( <a data-test-id="link" className={classes} {...otherProps} /*     */ /> ) } 

Ce modèle est également utile pour accepter des gestionnaires d'événements (par exemple, onClick ) pour un composant qui les possède déjà.


interrupteur


 <Switch onClick={value => console.log(value)} /> 

Voici à quoi ressemble l'implémentation de ce composant:


 class Switch extends React.Component { state = { enabled: false } onToggle = event => { /*      */ this.setState({ enabled: !this.state.enabled }) /*       */ if (typeof this.props.onClick === 'function') { this.props.onClick(event, this.state.enabled) } } render() { /*        ️ */ return <div class="toggler" onClick={this.onToggle} /> } } 

Il existe un autre moyen d'éviter les conflits de noms dans les gestionnaires d'événements, que j'ai décrit dans l' API d'écriture pour les composants React, Partie 2: Donnez des noms au comportement, pas des moyens d'interaction .




Pour chaque scénario, vous pouvez utiliser différentes approches.


  1. La plupart du temps: le développeur doit pouvoir modifier la valeur de l'accessoire, dont la valeur a été définie par défaut
  2. Généralement pour les styles et les gestionnaires d'événements: le développeur doit pouvoir ajouter une valeur par rapport à la valeur par défaut
  3. Un cas rare où vous devez limiter les actions du développeur: le développeur n'est pas autorisé à modifier le comportement, vous devez ignorer ses valeurs et, en même temps, afficher des avertissements

Source: https://habr.com/ru/post/fr459380/


All Articles