Pourquoi les accessoires JSX ne devraient-ils pas utiliser les fonctions fléchées ou se lier?

103

J'exécute lint avec mon application React et je reçois cette erreur:

error    JSX props should not use arrow functions        react/jsx-no-bind

Et c'est là que j'exécute la fonction flèche (à l'intérieur onClick):

{this.state.photos.map(tile => (
  <span key={tile.img}>
    <Checkbox
      defaultChecked={tile.checked}
      onCheck={() => this.selectPicture(tile)}
      style={{position: 'absolute', zIndex: 99, padding: 5, backgroundColor: 'rgba(255, 255, 255, 0.72)'}}
    />
    <GridTile
      title={tile.title}
      subtitle={<span>by <b>{tile.author}</b></span>}
      actionIcon={<IconButton onClick={() => this.handleDelete(tile)}><Delete color="white"/></IconButton>}
    >
      <img onClick={() => this.handleOpen(tile.img)} src={tile.img} style={{cursor: 'pointer'}}/>
    </GridTile>
  </span>
))}

Est-ce une mauvaise pratique à éviter? Et quelle est la meilleure façon de le faire?

KadoBOT
la source

Réponses:

170

Pourquoi vous ne devriez pas utiliser les fonctions de flèche en ligne dans les accessoires JSX

L'utilisation de fonctions fléchées ou de liaison dans JSX est une mauvaise pratique qui nuit aux performances, car la fonction est recréée à chaque rendu.

  1. Chaque fois qu'une fonction est créée, la fonction précédente est garbage collection. Le rendu de nombreux éléments peut créer des saccades dans les animations.

  2. L'utilisation d'une fonction de flèche en ligne provoquera le rendu de PureComponents et des composants utilisés shallowComparedans la shouldComponentUpdateméthode. Étant donné que l'accessoire de fonction de flèche est recréé à chaque fois, la comparaison superficielle l'identifiera comme une modification apportée à un accessoire et le composant sera renvoyé.

Comme vous pouvez le voir dans les 2 exemples suivants - lorsque nous utilisons la fonction de flèche en ligne, le <Button>composant est rendu à chaque fois (la console affiche le texte du 'bouton de rendu').

Exemple 1 - PureComponent sans gestionnaire en ligne

Exemple 2 - PureComponent avec un gestionnaire en ligne

Liaison de méthodes à des thisfonctions fléchées sans insertion

  1. Lier la méthode manuellement dans le constructeur:

    class Button extends React.Component {
      constructor(props, context) {
        super(props, context);
    
        this.cb = this.cb.bind(this);
      }
    
      cb() {
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }
    
  2. Lier une méthode à l'aide des champs de classe de proposition avec une fonction de flèche. Comme il s'agit d'une proposition de l'étape 3, vous devrez ajouter le préréglage de l' étape 3 ou la transformation des propriétés de classe à votre configuration babel.

    class Button extends React.Component {
      cb = () => { // the class property is initialized with an arrow function that binds this to the class
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }
    

Composants de fonction avec rappels internes

Lorsque nous créons une fonction interne (gestionnaire d'événements par exemple) à l'intérieur d'un composant de fonction, la fonction sera recréée à chaque fois que le composant est rendu. Si la fonction est passée en tant qu'accessoires (ou via le contexte) à un composant enfant ( Buttondans ce cas), cet enfant sera également rendu.

Exemple 1 - Composant de fonction avec un rappel interne:

Pour résoudre ce problème, nous pouvons encapsuler le rappel avec le useCallback()hook et définir les dépendances sur un tableau vide.

Remarque: la useStatefonction générée accepte une fonction de mise à jour, qui fournit l'état actuel. De cette façon, nous n'avons pas besoin de définir l'état actuel comme une dépendance de useCallback.

Exemple 2 - Composant de fonction avec un rappel interne enveloppé avec useCallback:

Ori Drori
la source
3
Comment y parvenir sur des composants sans état?
lux
4
Les composants (de fonction) sans état n'ont pas this, il n'y a donc rien à lier. Habituellement, les méthodes sont fournies par un composant intelligent wrapper.
Ori Drori
39
@OriDrori: Comment cela fonctionne-t-il lorsque vous devez transmettre des données dans le rappel? onClick={() => { onTodoClick(todo.id) }
adam-beck
4
@ adam-beck - ajoutez-le dans la définition de la méthode de rappel dans la classe cb() { onTodoClick(this.props.todo.id); }.
Ori Drori
2
@ adam-beck Je pense que c'est comment utiliser useCallbackavec une valeur dynamique. stackoverflow.com/questions/55006061/…
Shota Tamura
9

C'est parce qu'une fonction de flèche créera apparemment une nouvelle instance de la fonction sur chaque rendu si elle est utilisée dans une propriété JSX. Cela pourrait créer une énorme pression sur le ramasse-miettes et empêchera également le navigateur d'optimiser les "chemins chauds" puisque les fonctions seront jetées au lieu d'être réutilisées.

Vous pouvez voir toute l'explication et quelques informations supplémentaires sur https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

Karl-Johan Sjögren
la source
Non seulement que. La création de nouvelles instances de fonction à chaque fois signifie que l'état est modifié et lorsque l'état d'un composant est modifié, il sera rendu à nouveau. Étant donné que l'une des principales raisons d'utiliser React est de ne rendre que les éléments qui changent, l'utilisation des bindfonctions ou des flèches consiste ici à vous tirer une balle dans le pied. Ce n'est pas bien documenté cependant, en particulier dans le cas de l'utilisation de maptableaux de ping dans les listes, etc.
hippietrail
"Créer les nouvelles instances de fonction à chaque fois signifie que l'état est modifié" qu'entendez-vous par là? Il n'y a pas d'état du tout dans la question
apieceofbart
4

Pour éviter de créer de nouvelles fonctions avec les mêmes arguments, vous pouvez mémoriser le résultat de la liaison de fonction, voici un simple utilitaire nommé memobindpour le faire: https://github.com/supnate/memobind

supNate
la source
4

Utiliser des fonctions en ligne comme celle-ci est parfaitement bien. La règle du peluchage est dépassée.

Cette règle date d'une époque où les fonctions fléchées n'étaient pas aussi courantes et où les gens utilisaient .bind (this), qui était lent. Le problème de performances a été résolu dans Chrome 49.

Faites attention à ne pas transmettre de fonctions en ligne comme accessoires à un composant enfant.

Ryan Florence, l'auteur de React Router, a écrit un excellent article à ce sujet:

https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578

sbaechler
la source
Pouvez-vous s'il vous plaît montrer comment écrire un test unitaire sur des composants avec des fonctions fléchées en ligne?
krankuba
1
@krankuba Ce n'est pas le sujet de cette question. Vous pouvez toujours transmettre des fonctions anonymes qui ne sont pas définies en ligne mais qui ne sont toujours pas testables.
sbaechler
-1

Vous pouvez utiliser les fonctions fléchées à l'aide de la bibliothèque react-cached-handler , pas besoin de s'inquiéter des performances de re-rendu:

Remarque: en interne, il met en cache vos fonctions fléchées par la touche spécifiée, pas besoin de s'inquiéter du rendu!

render() {

  return <div>
  {
        this.props.photos.map(photo=>
          <Photo key={photo.url}
            onClick={this.handler(photo.url, (url) => { 
                 console.log(url) })}
          />)
   }
 </div>

}

Autres caractéristiques:

  • Gestionnaires nommés
  • Gérer les événements par des fonctions fléchées
  • Accès à la clé, aux arguments personnalisés et à l'événement d'origine
  • Rendu des composants Performace
  • Contexte personnalisé pour les gestionnaires
Ghominejad
la source
La question était de savoir pourquoi ne pouvons-nous pas l'utiliser. Pas comment l'utiliser avec un autre hack.
kapil
-1

Pourquoi les accessoires JSX ne devraient-ils pas utiliser les fonctions fléchées ou se lier?

Surtout, parce que les fonctions en ligne peuvent interrompre la mémorisation des composants optimisés:

Traditionnellement, les problèmes de performances concernant les fonctions en ligne dans React étaient liés à la façon dont le passage de nouveaux rappels sur chaque rendu interrompt les shouldComponentUpdateoptimisations dans les composants enfants. ( docs )

Il s'agit moins d'un coût de création de fonction supplémentaire:

Les problèmes de performances ont Function.prototype.bind été résolus ici et les fonctions fléchées sont soit natives, soit transposées par babel en fonctions simples; dans les deux cas, nous pouvons supposer que ce n'est pas lent. ( Formation Réagir )

Je pense que les gens qui prétendent que la création de fonctions coûte cher ont toujours été mal informés (l'équipe React n'a jamais dit cela). ( Tweet )

Quand la react/jsx-no-bindrègle est-elle utile?

Vous voulez vous assurer que les composants mémorisés fonctionnent comme prévu:

  • React.memo (pour les composants de fonction)
  • PureComponentou personnalisé shouldComponentUpdate(pour les composants de classe)

En obéissant à cette règle, des références d'objet de fonction stables sont transmises. Ainsi, les composants ci-dessus peuvent optimiser les performances en empêchant les ré-rendus, lorsque les accessoires précédents n'ont pas changé.

Comment résoudre l'erreur ESLint?

Classes: définissez le gestionnaire comme méthode ou propriété de classe pour la thisliaison.
Crochets: utilisez useCallback.

Terrain d'entente

Dans de nombreux cas, les fonctions en ligne sont très pratiques à utiliser et absolument parfaites en termes d'exigences de performances. Malheureusement, cette règle ne peut pas être limitée aux seuls types de composants mémorisés. Si vous souhaitez toujours l'utiliser dans tous les domaines, vous pouvez par exemple le désactiver pour les nœuds DOM simples:

rules: {
  "react/jsx-no-bind": [ "error", { ignoreDOMComponents: true } ],
}

const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
ford04
la source