É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:

<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 => { 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
?

<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:
- Un développeur utilisant notre composant devrait pouvoir remplacer la valeur de prop par défaut.
- Nous ne voulons pas laisser le développeur changer certains accessoires
- 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:

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
:

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

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

<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 => { 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à.

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