Comment décider, comment choisir entre ces trois en fonction de l'objectif/de la taille/des fonctions/du comportement de nos composants ?
S'étendant de React.PureComponent
ou de React.Component
avec un shouldComponentUpdate
ont des répercussions sur les performances. L'utilisation de composants fonctionnels apatrides 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. Ainsi, vous êtes sûr qu'ils sont découplés de la logique de l'application, qu'ils sont faciles à tester et qu'ils n'ont pas d'effets secondaires inattendus. L'exception est si, pour une raison quelconque, vous avez beaucoup d'entre eux ou si vous avez vraiment besoin d'optimiser leur méthode de rendu (car vous ne pouvez pas définir shouldComponentUpdate
pour un composant fonctionnel sans état).
-
Étendre le site PureComponent
si vous savez que votre sortie dépend de props/états simples ("simple" signifie pas de structures de données imbriquées, car PureComponent effectue une comparaison superficielle) ET que vous avez besoin/possédez des améliorations de performances.
-
Étendre le site Component
et mettez en œuvre votre propre shouldComponentUpdate
si vous avez besoin de gains de performance en effectuant une logique de comparaison personnalisée entre les props et les états suivants/actuels. 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 même, la mise en œuvre de votre propre shouldComponentUpdate
ou s'étendant de PureComponent
sont des optimisations, et comme d'habitude, vous ne devriez commencer à les examiner que si vous avez des problèmes de performance ( éviter les optimisations prématurées ). En règle générale, j'essaie toujours d'effectuer ces optimisations une fois que l'application est en état de marche, avec la plupart des fonctionnalités déjà mises en œuvre. Il est beaucoup plus facile de se concentrer sur les problèmes de performances lorsqu'ils sont réellement gênants.
Plus de détails
Composants apatrides fonctionnels :
Ceux-ci sont définis en utilisant simplement 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 props donnés en entrée de cette fonction.
Pour :
-
La façon la plus simple possible de définir un composant dans React. Si vous n'avez pas besoin de gérer d'état, pourquoi s'embêter avec les classes et 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 ne dépend que de l'entrée (pas de l'historique des exécutions précédentes).
-
Idéalement, dans votre application, vous devriez chercher à avoir le plus grand nombre possible de composants sans état, car cela signifie normalement que vous avez déplacé votre logique en dehors de la couche d'affichage et que vous l'avez déplacée vers quelque chose comme redux, ce qui signifie que vous pouvez tester votre logique réelle sans avoir à effectuer de rendu (beaucoup plus facile à tester, plus réutilisable, etc.).
Cons :
-
Aucune méthode de cycle de vie. Vous n'avez pas la possibilité de définir componentDidMount
et d'autres amis. Normalement, vous le faites dans un composant parent situé plus haut dans la hiérarchie afin de transformer tous les enfants en composants apatrides.
-
Il n'y a aucun moyen de contrôler manuellement quand un nouveau rendu est nécessaire, puisque vous ne pouvez pas définir shouldComponentUpdate
. Un nouveau rendu se produit à chaque fois que le composant reçoit de nouveaux props (pas de moyen de comparer superficiellement, etc.). Dans le futur, React pourrait optimiser automatiquement les composants sans état, pour l'instant il y a quelques bibliothèques que vous pouvez utiliser. Puisque les composants apatrides sont juste des fonctions, c'est le problème classique de la "mémorisation des 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 l'habitude d'avoir un PureRenderMixin
vous pourriez vous attacher à une classe définie à l'aide de React.createClass
syntaxe. Le mixin définirait simplement un shouldComponentUpdate
effectuer une comparaison superficielle entre le props suivant et l'état suivant pour vérifier si quelque chose y a changé. Si rien n'a changé, il n'est pas nécessaire d'effectuer un nouveau rendu.
Si vous voulez utiliser la syntaxe ES6, vous ne pouvez pas utiliser les mixins. Donc, pour des raisons de commodité, React a introduit un PureComponent
dont vous pouvez hériter au lieu d'utiliser Component
. PureComponent
ne fait que mettre en œuvre shouldComponentUpdate
de la même manière que le PureRendererMixin
. Il s'agit surtout d'une commodité pour que vous n'ayez 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 permettre de gagner rapidement en performance.
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.imageUrl
y props.username
. Si dans un composant parent vous rendez <UserAvatar username="fabio" imageUrl="http://foo.com/fabio.jpg" />
avec les mêmes props, React appellerait render
à chaque fois, même si le résultat est exactement le même. Rappelez-vous cependant que React implémente le dom diffing, donc le DOM ne sera pas réellement mis à jour. Néanmoins, l'exécution du dom diffing peut être coûteuse, donc dans ce scénario, ce serait un gaspillage.
Si le UserAvatar
Le composant s'étend PureComponent
à la place, une comparaison superficielle est effectuée. Et parce que props et nextProps sont les mêmes, render
ne sera pas appelé du tout.
Notes sur la définition de "pure" dans React :
En général, une "fonction pure" est une fonction dont l'évaluation donne toujours le même résultat pour la même entrée. La sortie (pour React, c'est ce qui est retourné par la fonction render
) ne dépend d'aucun historique/état et n'a pas d'effets secondaires (opérations qui modifient le "monde" en dehors de la fonction).
Dans React, les composants apatrides ne sont pas nécessairement des composants purs selon la définition ci-dessus si vous appelez "apatride" un composant qui n'appelle jamais this.setState
et qui n'utilise pas this.state
.
En fait, dans un 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 de la méthode componentDidMount
ou vous pouvez effectuer des calculs DOM pour ajuster dynamiquement la hauteur d'un div à l'intérieur du fichier render
.
La définition des "composants muets" a une signification plus "pratique" (du moins selon ma compréhension) : un composant muet "se fait dire" quoi faire par un composant parent via les props, et ne sait pas comment faire les choses mais utilise les callbacks des props à la place.
Exemple d'un "smart" (intelligent) 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 d'un "muet 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 les notions de "muette", "apatride" et "pure" sont des concepts très différents qui peuvent parfois se chevaucher, mais pas nécessairement, en fonction principalement de votre cas d'utilisation.