Aujourd'hui, nous publions une traduction du matériel, dont l'auteur, après avoir analysé les caractéristiques du travail avec des objets en JavaScript, propose aux développeurs de React une méthodologie pour accélérer les applications. En particulier, nous parlons du fait qu'une variable, qui, comme on dit, est "affectée à un objet", et qui est souvent appelée simplement "objet", en fait, ne stocke pas l'objet lui-même, mais un lien vers celui-ci. Les fonctions en JavaScript sont également des objets, donc ce qui précède est vrai pour eux. Gardant cela à l'esprit, la conception de composants React et l'analyse critique de leur code peuvent améliorer leurs mécanismes internes et améliorer les performances des applications.
Fonctionnalités de travail avec des objets en JavaScript
Si vous créez quelques fonctions qui se ressemblent exactement et que vous essayez de les comparer, il s'avère qu'elles sont différentes du point de vue du système. Afin de vérifier cela, vous pouvez exécuter le code suivant:
const functionOne = function() { alert('Hello world!'); }; const functionTwo = function() { alert('Hello world!'); }; functionOne === functionTwo;
Essayons maintenant d'affecter une variable à une fonction existante déjà affectée à une autre variable et comparons ces deux variables:
const functionThree = function() { alert('Hello world!'); }; const functionFour = functionThree; functionThree === functionFour;
Comme vous pouvez le voir, avec cette approche, l'opérateur d'égalité stricte renvoie
true
.
Les objets se comportent naturellement de la même manière:
const object1 = {}; const object2 = {}; const object3 = object1; object1 === object2; // false object1 === object3; // true
Ici, nous parlons de JavaScript, mais si vous avez de l'expérience dans le développement dans d'autres langues, vous connaissez peut-être le concept de pointeurs. Dans le code ci-dessus, chaque fois qu'un objet est créé, une partie de la mémoire système lui est allouée. Lorsque nous utilisons une commande de la forme
object1 = {}
, cela conduit à remplir avec certaines données un morceau de mémoire alloué spécifiquement pour
object1
.
Il est tout à fait possible d'imaginer
object1
comme l'adresse à laquelle les structures de données liées à l'objet sont situées en mémoire. L'exécution de la commande
object2 = {}
conduit à l'allocation d'une autre zone mémoire, conçue spécifiquement pour
object2
.
obect1
et
obect1
sont-
obect1
object2
dans la même zone de mémoire? Non, chacun d'eux a son propre complot. C'est pourquoi lorsque nous essayons de comparer
object1
et
object1
object2
nous obtenons
false
. Ces objets peuvent avoir une structure identique, mais les adresses dans la mémoire où ils se trouvent diffèrent, et ce sont les adresses qui sont vérifiées lors de la comparaison.
En exécutant la commande
object3 = object1
, nous écrivons l'adresse de
object1
dans la constante
object3
. Ce n'est pas un nouvel objet. Cette constante se voit attribuer l'adresse d'un objet existant. Vous pouvez le vérifier en:
const object1 = { x: true }; const object3 = object1; object3.x = false; object1.x;
Dans cet exemple, un objet est créé en mémoire et son adresse est écrite dans la constante
object1
. Ensuite, la même adresse est écrite dans la constante
object3
. Changer
object3
change l'objet en mémoire. Cela signifie que lorsque vous accédez à un objet en utilisant toute autre référence à celui-ci, par exemple celui stocké dans
object1
, nous travaillerons déjà avec sa version modifiée.
Fonctions, objets et réaction
La méconnaissance du mécanisme ci-dessus par les développeurs novices conduit souvent à des erreurs et, peut-être, la prise en compte des fonctionnalités de travail avec des objets mérite un article séparé. Cependant, notre sujet aujourd'hui est la performance des applications React. Dans ce domaine, des erreurs peuvent être commises même par des développeurs assez expérimentés qui ne font tout simplement pas attention à la façon dont les applications React sont affectées par le fait que les variables et les constantes JavaScript ne sont pas stockées dans les objets eux-mêmes, mais seulement des liens vers eux.
Qu'est-ce que cela a à voir avec React? React dispose de mécanismes intelligents pour économiser les ressources système visant à améliorer les performances des applications: si les propriétés et l'état du composant ne changent pas, alors ce que la fonction de
render
ne change pas. Évidemment, si le composant reste le même, il n'a pas besoin d'être rendu à nouveau. Si rien ne change, la fonction de
render
retournera la même chose qu'avant, il n'est donc pas nécessaire de l'exécuter. Ce mécanisme rend React rapide. Quelque chose ne s'affiche que lorsque cela est nécessaire.
React vérifie l'égalité des propriétés et de l'état des composants à l'aide de fonctionnalités JavaScript standard, c'est-à-dire qu'il les compare simplement à l'aide de l'opérateur
==
. React n'effectue pas de comparaison «superficielle» ou «profonde» d'objets afin de déterminer leur égalité. Une «comparaison superficielle» est un concept utilisé pour décrire une comparaison de chaque paire clé-valeur d'un objet, par opposition à une comparaison dans laquelle seules les adresses des objets en mémoire sont comparées (références à celles-ci). La comparaison «profonde» des objets va encore plus loin, et si les valeurs des propriétés comparées des objets sont également des objets, elles comparent également les paires clé-valeur de ces objets. Ce processus est répété pour tous les objets imbriqués dans d'autres objets. React ne fait rien de tel, vérifiant simplement l'égalité des liens.
Si, par exemple, vous modifiez la propriété d'un composant représenté par un objet de la forme
{ x: 1 }
en un autre objet qui a exactement la même apparence, React restitue le composant, car ces objets se trouvent dans différentes zones de mémoire. Si vous vous souvenez de l'exemple ci-dessus, alors, lorsque vous changez les propriétés d'un composant d'
object1
en
object3
, React ne restitue pas un tel composant, car les constantes
object1
et
object3
font référence au même objet.
L'utilisation des fonctions en JavaScript est organisée exactement de la même manière. Si React rencontre les mêmes fonctionnalités dont les adresses sont différentes, il sera restitué. Si la «nouvelle fonction» n'est qu'un lien vers une fonction qui a déjà été utilisée, il n'y aura pas de nouveau rendu.
Un problème typique lors de l'utilisation de composants
Voici l'un des scénarios de travail avec des composants, qui, malheureusement, me revient constamment à l'esprit lors de la vérification du code de quelqu'un d'autre:
class SomeComponent extends React.PureComponent { get instructions() { if (this.props.do) { return 'Click the button: '; } return 'Do NOT click the button: '; } render() { return ( <div> {this.instructions} <Button onClick={() => alert('!')} /> </div> ); } }
Devant nous est un composant très simple. Il s'agit d'un bouton, lorsque vous cliquez dessus, une notification s'affiche. À côté du bouton sont affichées des instructions pour son utilisation, informant l'utilisateur s'il doit appuyer sur ce bouton. Contrôlez quelle instruction sera affichée en définissant la
SomeComponent
do
(
do={true}
ou
do={false}
)
SomeComponent
.
Chaque fois que le composant
SomeComponent
est restitué (lorsque la valeur de la propriété
do
passe de
true
à
false
et vice versa), l'élément
Button
est également rendu. Le gestionnaire
onClick
, bien qu'il soit toujours le même, est recréé à chaque appel de la fonction de
render
. Par conséquent, il s'avère que chaque fois que le composant est affiché en mémoire, une nouvelle fonction est créée, puisque sa création est effectuée dans la fonction de
render
, un lien vers la nouvelle adresse en mémoire est transmis à
<Button />
, et le composant
Button
est également rendu à nouveau, malgré le fait que dans rien n'a changé du tout.
Parlons de la façon de le réparer.
Résolution de problèmes
Si la fonction est indépendante du composant (
this
contexte), vous pouvez la définir en dehors du composant. Toutes les instances du composant utiliseront la même référence de fonction, car dans tous les cas, ce sera la même fonction. Voici à quoi ça ressemble:
const createAlertBox = () => alert('!'); class SomeComponent extends React.PureComponent { get instructions() { if (this.props.do) { return 'Click the button: '; } return 'Do NOT click the button: '; } render() { return ( <div> {this.instructions} <Button onClick={createAlertBox} /> </div> ); } }
Contrairement à l'exemple précédent,
createAlertBox
, avec chaque appel à
render
, contiendra le même lien vers la même zone en mémoire. Par conséquent,
Button
sortie répétée
Button
ne sera pas exécutée.
Alors que le composant
Button
est petit et rendu rapidement, le problème décrit ci-dessus associé à la déclaration interne des fonctions peut également être trouvé dans les grands composants complexes qui prennent beaucoup de temps à rendre. Cela peut ralentir considérablement l'application React. À cet égard, il est logique de suivre la recommandation, selon laquelle de telles fonctions ne devraient jamais être déclarées dans la méthode de
render
.
Si la fonction dépend du composant, c'est-à-dire qu'elle ne peut pas être définie en dehors de celui-ci, la méthode du composant peut être passée en tant que gestionnaire d'événements:
class SomeComponent extends React.PureComponent { createAlertBox = () => { alert(this.props.message); }; get instructions() { if (this.props.do) { return 'Click the button: '; } return 'Do NOT click the button: '; } render() { return ( <div> {this.instructions} <Button onClick={this.createAlertBox} /> </div> ); } }
Dans ce cas, dans chaque instance de
SomeComponent
lorsque vous cliquez sur le bouton, divers messages s'affichent. Le gestionnaire d'événements de l'élément
Button
doit être unique à
SomeComponent
. Lors du passage de la méthode
cteateAlertBox
, peu importe si
SomeComponent
restitué. Peu importe si la propriété du
message
a changé. L'adresse de la fonction
createAlertBox
change pas, ce qui signifie que l'élément
Button
ne doit pas être restitué. Grâce à cela, vous pouvez économiser des ressources système et améliorer la vitesse de rendu de l'application.
Tout cela est bien. Mais que faire si les fonctions sont dynamiques?
Résoudre un problème plus complexe
L'auteur de ce document vous demande de prêter attention au fait qu'il a préparé les exemples de cette section, en prenant la première chose qui lui est venue à l'esprit, appropriée pour illustrer la réutilisation des fonctions. Ces exemples sont destinés à aider le lecteur à saisir l'essence de l'idée. Bien que cette section soit recommandée à la lecture pour comprendre l'essence de ce qui se passe, l'auteur conseille de prêter attention aux commentaires sur l' article original , car certains lecteurs ont suggéré de meilleures versions des mécanismes discutés ici, qui prennent en compte les fonctionnalités d'invalidation du cache et les mécanismes de gestion de la mémoire intégrés dans React.Ainsi, il est extrêmement courant que dans un composant, il existe de nombreux gestionnaires d'événements dynamiques uniques, par exemple, quelque chose de similaire peut être vu dans le code, où la méthode de tableau de
map
est utilisée dans la méthode de
render
:
class SomeComponent extends React.PureComponent { render() { return ( <ul> {this.props.list.map(listItem => <li key={listItem.text}> <Button onClick={() => alert(listItem.text)} /> </li> )} </ul> ); } }
Ici, un nombre différent de boutons sera affiché et un nombre différent de gestionnaires d'événements seront créés, chacun étant représenté par une fonction unique, et, à l'avance, lors de la création de
SomeComponent
, on ne sait pas quelles seront ces fonctions. Comment résoudre ce puzzle?
Ici, la mémorisation nous aidera, ou, plus simplement, la mise en cache. Pour chaque valeur unique, créez une fonction et placez-la dans le cache. Si cette valeur unique se reproduit, il suffira de prendre dans le cache la fonction qui lui correspond, qui était auparavant placée dans le cache.
Voici à quoi ressemble la mise en œuvre de cette idée:
class SomeComponent extends React.PureComponent {
Chaque élément du tableau est traité par la méthode
getClickHandler
. Cette méthode, la première fois qu'elle sera appelée avec une certaine valeur, créera une fonction unique à cette valeur, la placera dans le cache et la renverra. Tous les appels ultérieurs à cette méthode, en lui passant la même valeur, la feront simplement renvoyer un lien vers la fonction à partir du cache.
Par conséquent, le nouveau rendu de
SomeComponent
ne rendra pas le
Button
. De même, l'ajout d'éléments à la propriété
list
créera dynamiquement des gestionnaires d'événements pour chaque bouton.
Vous devrez être créatif dans la création d'identifiants uniques pour les gestionnaires s'ils sont définis par plusieurs variables, mais ce n'est pas beaucoup plus compliqué que la création habituelle d'une propriété de
key
unique pour chaque objet JSX obtenu à la suite de la méthode
map
.
Ici, je voudrais vous mettre en garde contre d'éventuels problèmes d'utilisation d'index de tableau comme identificateurs. Le fait est qu'avec cette approche, vous pouvez rencontrer des erreurs si l'ordre des éléments du tableau change ou si certains de ses éléments sont supprimés. Ainsi, par exemple, si au début un tableau similaire ressemblait à
[ 'soda', 'pizza' ]
, puis transformé en
[ 'pizza' ]
, et que vous mettiez en cache les gestionnaires d'événements à l'aide d'une commande de la forme
listeners[0] = () => alert('soda')
, vous constaterez que lorsque l'utilisateur clique sur le bouton auquel le gestionnaire avec l'identifiant 0 est affecté et qui, conformément au contenu du tableau
[ 'pizza' ]
, devrait afficher un message de
pizza
, un message de
soda
sera affiché. Pour la même raison, il n'est pas recommandé d'utiliser des indices de tableau comme propriétés de clé.
Résumé
Dans cet article, nous avons examiné les fonctionnalités des mécanismes internes JavaScript, en envisageant la possibilité d'accélérer le rendu des applications React. Nous espérons que les idées présentées ici vous seront utiles.
Chers lecteurs! Si vous connaissez des moyens intéressants d'optimiser les applications React, partagez-les.
