Composant sans état fonctionnel React, PureComponent, Component; quelles sont les différences et quand devons-nous utiliser quoi?


191

J'ai appris qu'à partir de React v15.3.0 , nous avons une nouvelle classe de base appelée PureComponent à étendre avec PureRenderMixin intégré. Ce que je comprends, c'est que, sous le capot, cela utilise une comparaison superficielle des accessoires à l'intérieur shouldComponentUpdate.

Nous avons maintenant 3 façons de définir un composant React:

  1. Composant sans état fonctionnel qui n'étend aucune classe
  2. Un composant qui étend la PureComponentclasse
  3. Un composant normal qui étend la Componentclasse

Il y a quelque temps, nous appelions les composants sans état comme des composants purs, ou même des composants stupides. On dirait que toute la définition du mot «pur» a maintenant changé dans React.

Bien que je comprenne les différences fondamentales entre ces trois éléments, je ne sais toujours pas quand choisir quoi . Quels sont également les impacts sur la performance et les compromis de chacun?


Mettre à jour :

Voici la question que je compte clarifier:

  • Dois-je choisir de définir mes composants simples comme fonctionnels (par souci de simplicité) ou d'étendre la PureComponentclasse (pour des raisons de performances)?
  • L'augmentation des performances que j'obtiens est-elle un véritable compromis avec la simplicité que j'ai perdue?
  • Aurais-je besoin d'étendre la Componentclasse normale alors que je peux toujours l'utiliser PureComponentpour de meilleures performances?

Réponses:


317

Comment décidez-vous, comment choisissez-vous entre ces trois éléments en fonction de l'objectif / de la taille / des accessoires / du comportement de nos composants?

L'extension à partir React.PureComponentou à partir React.Componentd'une shouldComponentUpdateméthode personnalisée a des implications sur les performances. L'utilisation de composants fonctionnels sans état est un choix «architectural» et ne présente pas (encore) d'avantages en termes de performances.

  • Pour les composants simples, uniquement de présentation qui doivent être facilement réutilisés, préférez les composants fonctionnels sans état. De cette façon, vous êtes sûr qu'ils sont découplés de la logique réelle de l'application, qu'ils sont extrêmement faciles à tester et qu'ils n'ont pas d'effets secondaires inattendus. L'exception est si, pour une raison quelconque, vous en avez beaucoup ou si vous avez vraiment besoin d'optimiser leur méthode de rendu (comme vous ne pouvez pas définir shouldComponentUpdatepour un composant fonctionnel sans état).

  • Étendez PureComponentsi vous savez que votre sortie dépend de simples accessoires / état («simple» signifie pas de structures de données imbriquées, car PureComponent effectue une comparaison superficielle) ET vous avez besoin / pouvez obtenir des améliorations de performances.

  • Étendez Componentet implémentez les vôtres shouldComponentUpdatesi vous avez besoin de gains de performances en exécutant une logique de comparaison personnalisée entre les accessoires et l'état suivant / actuel. Par exemple, vous pouvez rapidement effectuer une comparaison approfondie en utilisant lodash # isEqual:

    class MyComponent extends Component {
        shouldComponentUpdate (nextProps, nextState) {
            return !_.isEqual(this.props, nextProps) || !_.isEqual(this.state, nextState);
        }
    }
    

De plus, implémenter les vôtres shouldComponentUpdateou étendre à partir de PureComponentsont des optimisations, et comme d'habitude, vous ne devriez commencer à vous pencher sur cela que si vous avez des problèmes de performances ( évitez les optimisations prématurées ). En règle générale, j'essaie toujours de faire ces optimisations une fois que l'application est en état de fonctionnement, la plupart des fonctionnalités étant déjà implémentées. Il est beaucoup plus facile de se concentrer sur les problèmes de performance lorsqu'ils sont réellement gênants.

Plus de détails

Composants fonctionnels sans état:

Celles-ci sont définies simplement à l'aide d'une fonction. Puisqu'il n'y a pas d'état interne pour un composant sans état, la sortie (ce qui est rendu) dépend uniquement des accessoires donnés en entrée de cette fonction.

Avantages:

  • La manière la plus simple possible de définir un composant dans React. Si vous n'avez besoin de gérer aucun état, pourquoi vous soucier des classes et de l'héritage? L'une des principales différences entre une fonction et une classe est qu'avec la fonction, vous êtes sûr que la sortie dépend uniquement de l'entrée (et non de l'historique des exécutions précédentes).

  • Idéalement, dans votre application, vous devriez viser à avoir autant de composants sans état que possible, car cela signifie normalement que vous avez déplacé votre logique en dehors de la couche de vue et l'avez déplacée vers quelque chose comme redux, ce qui signifie que vous pouvez tester votre logique réelle sans avoir à rendre quoi que ce soit. (beaucoup plus facile à tester, plus réutilisable, etc.).

Les inconvénients:

  • Aucune méthode de cycle de vie. Vous n'avez pas de moyen de définir componentDidMountet d'autres amis. Normalement, vous faites cela dans un composant parent plus haut dans la hiérarchie afin de pouvoir transformer tous les enfants en enfants sans état.

  • Aucun moyen de contrôler manuellement le moment où un nouveau rendu est nécessaire, car vous ne pouvez pas définir shouldComponentUpdate. Un nouveau rendu se produit chaque fois que le composant reçoit de nouveaux accessoires (aucun moyen de comparaison superficielle, etc.). À l'avenir, React pourrait optimiser automatiquement les composants sans état, pour l'instant, vous pouvez utiliser certaines bibliothèques. Puisque les composants sans état ne sont que des fonctions, c'est fondamentalement le problème classique de la "mémorisation de fonctions".

  • Les références ne sont pas prises en charge: https://github.com/facebook/react/issues/4936

Un composant qui étend la classe PureComponent VS Un composant normal qui étend la classe Component:

React avait un que PureRenderMixinvous pouviez attacher à une classe définie à l'aide de la React.createClasssyntaxe. Le mixin définirait simplement une shouldComponentUpdatecomparaison superficielle entre les accessoires suivants et l'état suivant pour vérifier si quelque chose a changé. Si rien ne change, il n'est pas nécessaire d'effectuer un nouveau rendu.

Si vous souhaitez utiliser la syntaxe ES6, vous ne pouvez pas utiliser les mixins. Donc, pour plus de commodité, React a introduit une PureComponentclasse dont vous pouvez hériter au lieu d'utiliser Component. PureComponentimplémente simplement shouldComponentUpdatede la même manière que le PureRendererMixin. C'est surtout une chose de commodité, vous n'avez donc pas à l'implémenter vous-même, car une comparaison superficielle entre l'état actuel / suivant et les accessoires est probablement le scénario le plus courant qui peut vous donner des gains de performances rapides.

Exemple:

class UserAvatar extends Component {
    render() {
       return <div><img src={this.props.imageUrl} /> {{ this.props.username }} </div>
    }
} 

Comme vous pouvez le voir, la sortie dépend de props.imageUrlet props.username. Si dans un composant parent vous effectuez <UserAvatar username="fabio" imageUrl="http://foo.com/fabio.jpg" />le rendu avec les mêmes accessoires, React appellerait à renderchaque fois, même si la sortie serait exactement la même. Souvenez-vous cependant que React implémente dom diffing, donc le DOM ne serait pas réellement mis à jour. Pourtant, effectuer la différence de dom peut être coûteux, donc dans ce scénario, ce serait un gaspillage.

Si le UserAvatarcomposant s'étend à la PureComponentplace, une comparaison superficielle est effectuée. Et comme les props et nextProps sont les mêmes, renderne seront pas du tout appelés.

Remarques sur la définition de "pur" dans React:

En général, une "fonction pure" est une fonction qui évalue toujours le même résultat avec la même entrée. La sortie (pour React, c'est ce qui est retourné par la renderméthode) ne dépend d'aucun historique / état et n'a aucun effet secondaire (opérations qui changent le «monde» en dehors de la fonction).

Dans React, les composants sans état ne sont pas nécessairement des composants purs selon la définition ci-dessus si vous appelez "sans état" un composant qui n'appelle jamais this.setStateet qui n'utilise pas this.state.

En fait, dans a PureComponent, vous pouvez toujours effectuer des effets secondaires pendant les méthodes du cycle de vie. Par exemple, vous pouvez envoyer une requête ajax à l'intérieur componentDidMountou effectuer un calcul DOM pour ajuster dynamiquement la hauteur d'un div à l'intérieur render.

La définition des "composants stupides" a une signification plus "pratique" (du moins dans ma compréhension): un composant stupide "se fait dire" quoi faire par un composant parent via des accessoires, et ne sait pas comment faire les choses mais utilise des accessoires rappels à la place.

Exemple de "smart" AvatarComponent:

class AvatarComponent extends Component {
    expandAvatar () {
        this.setState({ loading: true });
        sendAjaxRequest(...).then(() => {
            this.setState({ loading: false });
        });
    }        

    render () {
        <div onClick={this.expandAvatar}>
            <img src={this.props.username} />
        </div>
    }
}

Exemple de "stupide" AvatarComponent:

class AvatarComponent extends Component {
    render () {
        <div onClick={this.props.onExpandAvatar}>
            {this.props.loading && <div className="spinner" />}
            <img src={this.props.username} />
        </div>
    }
}

En fin de compte, je dirais que "stupide", "apatride" et "pur" sont des concepts assez différents qui peuvent parfois se chevaucher, mais pas nécessairement, en fonction principalement de votre cas d'utilisation.


1
J'apprécie vraiment votre réponse et les connaissances que vous avez partagées. Mais ma vraie question est de savoir quand devons-nous choisir quoi? . Pour le même exemple que vous avez mentionné dans votre réponse, comment devrais-je le définir? Doit-il être un composant sans état fonctionnel (si oui, pourquoi?), Ou étendre PureComponent (pourquoi?) Ou étendre la classe Component (encore une fois pourquoi?). Comment décidez-vous, comment choisissez-vous entre ces trois éléments en fonction de l' objectif / de la taille / des accessoires / du comportement de nos composants?
Yadhu Kiran

1
Aucun problème. Pour le composant fonctionnel sans état, il existe une liste des avantages / inconvénients que vous pouvez envisager pour décider si cela conviendrait. Cela répond-il à votre premier point? Je vais essayer d'aborder un peu plus la question du choix.
fabio.sussetto

2
Les composants fonctionnels sont toujours rendus lorsque le composant parent est mis à jour, même s'ils ne sont pas du tout utilisés props. exemple .
AlexM

1
C'est l'une des réponses les plus complètes que j'ai lues depuis un certain temps. Bon travail. Un commentaire sur la toute première phrase: lors de l'extension PureComponent, vous ne devez pas implémenter shouldComponentUpdate(). Vous devriez voir un avertissement si vous faites cela réellement.
jjramos

1
Pour de réels gains de performances, vous devriez essayer d'utiliser PureComponentpour les composants qui ont des propriétés d'objet / tableau imbriquées. Bien sûr, vous devez être conscient de ce qui se passe. Si je comprends bien, si vous ne mutez pas directement les accessoires / états (ce que React essaie de vous empêcher de faire avec les avertissements) ou via une bibliothèque externe, alors vous devriez bien utiliser PureComponentau lieu de Componentpresque partout ... à l'exception de composants très simples où il peut être plus rapide de ne pas l'utiliser - voir news.ycombinator.com/item?id=14418576
Matt Browne

27

Je ne suis pas un génie sur réagir, mais d'après ma compréhension, nous pouvons utiliser chaque composant dans les situations suivantes

  1. Composant sans état - ce sont les composants qui n'ont pas de cycle de vie, donc ces composants doivent être utilisés dans le rendu de l'élément de répétition du composant parent, comme le rendu de la liste de texte qui affiche simplement les informations et n'a aucune action à effectuer.

  2. Composant pur - ce sont les éléments qui ont un cycle de vie et ils renverront toujours le même résultat lorsqu'un ensemble spécifique d'accessoires est donné. Ces composants peuvent être utilisés lors de l'affichage d'une liste de résultats ou de données d'objet spécifiques qui n'ont pas d'éléments enfants complexes et utilisés pour effectuer des opérations qui n'ont un impact que sur elles-mêmes. une telle liste d'affichage des cartes d'utilisateur ou une liste de cartes de produits (informations de base sur le produit) et seule l'action que l'utilisateur peut effectuer est de cliquer pour afficher la page de détail ou de l'ajouter au panier.

  3. Composants normaux ou composants complexes - J'ai utilisé le terme composant complexe car ce sont généralement les composants au niveau de la page et se composent de nombreux composants enfants et puisque chacun des enfants peut se comporter de sa propre manière, vous ne pouvez donc pas être sûr à 100% qu'il le fera rendre le même résultat sur un état donné. Comme je l'ai dit habituellement, ils doivent être utilisés comme composants de conteneur


1
Cette approche pourrait fonctionner, mais vous pourriez manquer d'importants gains de performances. L'utilisation PureComponentde composants de niveau racine et de composants situés en haut de votre hiérarchie est généralement celle où vous constaterez les gains de performances les plus importants. Bien sûr, vous devez éviter la mutation des accessoires et de l'état directement pour que les composants purs fonctionnent correctement, mais la mutation directe des objets est de toute façon un anti-pattern dans React.
Matt Browne

5
  • React.Componentest le composant "normal" par défaut. Vous les déclarez à l'aide du classmot-clé et extends React.Component. Considérez-les comme une classe, avec des méthodes de cycle de vie, des gestionnaires d'événements et toutes les méthodes.

  • React.PureComponentest un React.Componentqui implémente shouldComponentUpdate()avec une fonction qui fait une comparaison superficielle de son propset state. Vous devez utiliser forceUpdate()si vous savez que le composant a des accessoires ou des données imbriquées d'état qui ont changé et que vous souhaitez effectuer un nouveau rendu. Ils ne sont donc pas parfaits si vous avez besoin de rendre des composants à nouveau lorsque des tableaux ou des objets que vous passez comme accessoires ou que vous définissez dans votre état changent.

  • Les composants fonctionnels sont ceux qui n'ont pas de fonctions de cycle de vie. Ils sont censés être apatrides, mais ils sont si beaux et propres que nous avons maintenant des hooks (depuis React 16.8) afin que vous puissiez toujours avoir un état. Donc je suppose que ce ne sont que des "composants propres".

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.