9 Conseils sur les performances de Vue

Cet article contient neuf conseils sur la façon d'améliorer les performances de votre application Vue, d'augmenter la vitesse d'affichage et de réduire la taille de l'ensemble.

Travailler avec des composants


Composants fonctionnels


Supposons que nous ayons un petit composant simple. Tout ce qu'il fait, c'est afficher une balise particulière en fonction de la valeur transmise:

<template> <div> <div v-if="value"></div> <section v-else></section> </div> </template> <script> export default { props: ['value'] } </script> 

Ce composant peut être optimisé en ajoutant l'attribut fonctionnel . Un composant fonctionnel se compile en une fonction simple et n'a pas d'état local. Pour cette raison, ses performances sont beaucoup plus élevées:

 <template functional> <div> <div v-if="props.value"></div> <section v-else></section> </div> </template> <script> export default { props: ['value'] } </script> 

Un exemple de ce à quoi les composants fonctionnels pourraient ressembler dans Vue v3.0

Séparation en composants enfants


Imaginez un composant à afficher dont vous avez besoin pour effectuer une tâche complexe:

 <template> <div :style="{ opacity: number / 300 }"> <div>{{ heavy() }}</div> </div> </template> <script> export default { props: ['number'], methods: { heavy () { /*   */ } } } </script> 

Le problème ici est que Vue exécutera la méthode heavy () chaque fois que le composant est rendu de nouveau, c'est-à-dire chaque fois que la valeur des accessoires change.

Nous pouvons facilement optimiser un tel composant si nous séparons la méthode lourde en un composant enfant:

 <template> <div :style="{ opacity: number / 300 }"> <ChildComp/> </div> </template> <script> export default { props: ['number'], components: { ChildComp: { methods: { heavy () { /*   */ } }, render (h) { return h('div', this.heavy()) } } } } </script> 

Pourquoi? Vue ignore automatiquement le rendu des composants dans lesquels les données dépendantes n'ont pas changé. Ainsi, lorsque les accessoires sont modifiés dans le composant parent, l'enfant sera réutilisé et la méthode heavy () ne sera pas redémarrée.

Notez que cela n'a de sens que si le composant enfant n'a pas de dépendance aux données dans le composant parent. Sinon, l'enfant sera recréé avec le parent, puis cette optimisation n'a pas de sens.

Obtient le cache local


Le composant suivant a une sorte de propriété calculée basée sur la deuxième propriété calculée:

 <template> <div :style="{ opacity: start / 300 }">{{ result }}</div> </template> <script> export default { props: ['start'], computed: { base () { return 42 }, result () { let result = this.start for (let i = 0; i < 1000; i++) { result += this.base } return result } } } </script> 

L'important ici est que la propriété de base est appelée dans une boucle, ce qui entraîne des complications. Chaque fois que vous accédez à des données réactives, Vue exécute une logique pour déterminer comment et à quelles données vous accédez pour créer des dépendances, etc. Ces petits frais généraux sont résumés s'il y a beaucoup d'appels, comme dans notre exemple.

Pour résoudre ce problème, accédez simplement à la base une fois et enregistrez la valeur dans une variable locale:

 <template> <div :style="{ opacity: start / 300 }">{{ result }}</div> </template> <script> export default { props: ['start'], computed: { base () { return 42 }, result () { const base = this.base // <-- let result = this.start for (let i = 0; i < 1000; i++) { result += base } return result } } } </script> 

Réutilisation de DOM avec v-show


Jetez un œil à l'exemple suivant:

 <template functional> <div> <div v-if="props.value"> <Heavy :n="10000"/> </div> <section v-else> <Heavy :n="10000"/> </section> </div> </template> 

Ici, nous avons un composant wrapper qui utilise v-if et v-else pour changer certains composants enfants.

Il est important de comprendre le fonctionnement du v-if. Chaque fois, lorsqu'un état est commuté, un composant enfant sera complètement détruit (le hook destroy () est appelé, tous les nœuds seront supprimés du DOM), et le second sera complètement créé et monté à nouveau. Et si ces composants sont «lourds», vous obtiendrez un blocage d'interface au moment de la commutation.

Solution plus productive - utilisez v-show

 <template functional> <div> <div v-show="props.value"> <Heavy :n="10000"/> </div> <section v-show="!props.value"> <Heavy :n="10000"/> </section> </div> </template> 

Dans ce cas, les deux composants enfants seront créés et montés immédiatement et existeront simultanément. Ainsi, Vue n'a pas besoin de détruire et de créer des composants lors de la commutation. Tout ce qu'il fait, c'est en cacher un et afficher le second avec CSS. Le changement d'état sera donc beaucoup plus rapide, mais vous devez comprendre que cela entraînera des coûts de mémoire élevés.

Utilisez <keep-alive>


Ainsi, un composant simple est un composant wrapper sur un routeur .

 <template> <div id="app"> <router-view/> </div> </template> 

Le problème est similaire à l'exemple précédent - tous les composants de votre routeur seront créés, montés et détruits lors des transitions entre les routes.

Et la solution ici est similaire - pour dire à Vue de ne pas détruire mais de mettre en cache et réutiliser les composants. Vous pouvez le faire en utilisant le composant <keep-alive> intégré spécial:

 <template> <div id="app"> <keep-alive> <router-view/> </keep-alive> </div> </template> 

Cette optimisation entraînera une augmentation de la consommation de mémoire, car Vue prendra en charge davantage de composants "vivants". Par conséquent, vous ne devez pas utiliser cette approche sans réfléchir pour tous les itinéraires, surtout si vous en avez beaucoup. Vous pouvez utiliser les attributs d'inclusion et d'exclusion pour définir les règles concernant les itinéraires à mettre en cache et ceux qui ne doivent pas:

 <template> <div id="app"> <keep-alive include="home,some-popular-page"> <router-view/> </keep-alive> </div> </template> 

Rendu différé


L'exemple suivant est un composant qui a plusieurs composants enfants très lourds:

 <template> <div> <h3>I'm an heavy page</h3> <Heavy v-for="n in 10" :key="n"/> <Heavy class="super-heavy" :n="9999999"/> </div> </template> 

Le problème est que le traitement des composants est une opération synchrone dans le thread principal. Et dans cet exemple, le navigateur n'affichera rien tant que le traitement de tous les composants, parents et enfants, n'est pas terminé. Et si cela prend beaucoup de temps pour traiter les filiales, vous obtiendrez des retards dans l'interface ou un écran vide pendant un certain temps.

Vous pouvez améliorer la situation en retardant le rendu des composants enfants:

 <template> <div> <h3>I'm an heavy page</h3> <template v-if="defer(2)"> <Heavy v-for="n in 10" :key="n"/> </template> <Heavy v-if="defer(3)" class="super-heavy" :n="9999999"/> </div> </template> <script> import Defer from '@/mixins/Defer' export default { mixins: [ Defer() ] } </script> 

Ici, la fonction defer (n) renvoie de fausses n trames. après quoi il revient toujours vrai. Il est utilisé pour reporter le traitement d'une partie du modèle par plusieurs images, donnant ainsi au navigateur la possibilité de dessiner l'interface.

Comment ça marche? Comme je l'ai écrit ci-dessus, si la condition dans la directive v-if est fausse, Vue ignore complètement une partie du modèle.

À la première image de l'animation, nous obtenons:

 <template> <div> <h3>I'm an heavy page</h3> <template v-if="false"> <Heavy v-for="n in 10" :key="n"/> </template> <Heavy v-if="false" class="super-heavy" :n="9999999"/> </div> </template> 

Vue monte simplement l'en-tête, et le navigateur l'affiche et demande une deuxième trame. À ce stade, defer (2) commencera à retourner vrai

 <template> <div> <h3>I'm an heavy page</h3> <template v-if="true"> <Heavy v-for="n in 10" :key="n"/> </template> <Heavy v-if="false" class="super-heavy" :n="9999999"/> </div> </template> 

Vue créera 10 composants enfants et les montera. Le navigateur les affichera et demandera la trame suivante, à laquelle le report (3) retournera vrai.

Ainsi, nous avons créé un composant lourd qui est traité progressivement, donnant au navigateur la possibilité d'afficher les parties déjà montées du modèle, ce qui améliorera considérablement l'UX car l'interface ne ressemblera pas à un blocage.

Code de report du mélangeur:

 export default function (count = 10) { return { data () { return { displayPriority: 0 } }, mounted () { this.runDisplayPriority() }, methods: { runDisplayPriority () { const step = () => { requestAnimationFrame(() => { this.displayPriority++ if (this.displayPriority < count) { step() } }) } step() }, defer (priority) { return this.displayPriority >= priority } } } } 

Chargement de composants paresseux


Parlons maintenant de l'importation de composants enfants:

 import Heavy from 'Heavy.js' export default { components: { Heavy } } 

Dans l'importation traditionnelle, le composant enfant est chargé immédiatement, lorsque le navigateur atteint l'instruction d'importation. Ou, si vous utilisez le collecteur, votre composant enfant sera inclus dans le bundle général.

Cependant, si votre composant enfant est très volumineux, il est judicieux de le charger de manière asynchrone.
C'est très simple à faire:

 const Heavy = () => import('Heavy.js') export default { components: { Heavy } } 

C’est tout ce dont vous avez besoin . Vue peut fonctionner avec des composants de chargement paresseux prêts à l'emploi. Il téléchargera lui-même le composant en cas de besoin et l'affichera dès qu'il sera prêt. Vous pouvez utiliser cette approche pour le chargement paresseux n'importe où.

Si vous utilisez le collecteur, tout ce qui concerne Heavy.js sera retiré dans un fichier séparé. Ainsi, vous réduirez le poids des fichiers lors du téléchargement initial et augmenterez la vitesse d'affichage.

Vues de chargement paresseux


Le chargement différé est très utile dans le cas des composants utilisés pour les itinéraires. Comme vous n'avez pas besoin de télécharger tous les composants pour les itinéraires en même temps, je vous recommande de toujours utiliser cette approche:

 const Home = () => import('Home.js') const About = () => import('About.js') const Contacts = () => import('Contacts.js') new VueRouter({ routes: [ { path: '/', component: Home }, { path: '/about', component: About }, { path: '/contacts', component: Contacts }, ] }) 

Chacun de ces composants sera chargé uniquement lorsque l'utilisateur demande pour la première fois l'itinéraire spécifié. Et pas avant.

Composants dynamiques


De même, vous pouvez facilement utiliser le chargement différé avec des composants dynamiques:

 <template> <div> <component :is="componentToShow"/> </div> </template> <script> export default { props: ['value'], computed: { componentToShow() { if (this.value) { return () => import('TrueComponent.js') } return () => import('FalseComponent.js') } } } </script> 

Encore une fois, chaque composant ne se charge que lorsqu'il est nécessaire.

Travailler avec Vuex


Évitez les gros commits


Imaginez que vous ayez besoin de stocker un large éventail de données dans le stockage:

 fetchItems ({ commit }, { items }) { commit('clearItems') commit('addItems', items) //   10000  } 

Le problème est le suivant. Toutes les validations sont des opérations synchrones. Cela signifie que le traitement d'un grand tableau bloquera votre interface pendant la durée du travail.

Pour résoudre ce problème, vous pouvez diviser le tableau en parties et les ajouter une à la fois, ce qui donne au navigateur le temps de dessiner un nouveau cadre:

 fetchItems ({ commit }, { items, splitCount }) { commit('clearItems') const queue = new JobQueue() splitArray(items, splitCount).forEach( chunk => queue.addJob(done => { //       requestAnimationFrame(() => { commit('addItems', chunk) done() }) }) ) //      await queue.start() } 

Plus les parties sont petites, plus vous ajoutez de données au stockage, plus votre interface restera fluide et plus le temps total pour terminer la tâche sera long.

De plus, avec cette approche, vous avez la possibilité d'afficher un indicateur de téléchargement pour l'utilisateur. Ce qui améliorera également considérablement son expérience avec l'application.

Désactivez la réactivité là où elle n'est pas nécessaire


Et le dernier exemple pour aujourd'hui. Nous avons une tâche similaire: nous ajoutons un tableau d'objets très volumineux au stockage avec un tas de niveaux d'imbrication:

 const data = items.map( item => ({ id: uid++, data: item, // <--    vote: 0 }) ) 

Le fait est que Vue traversera récursivement tous les champs imbriqués et les rendra réactifs. Si vous avez beaucoup de données, cela peut être coûteux, mais ce qui est beaucoup plus important - inutile.

Si votre application est conçue de telle manière qu'elle ne dépend que de l'objet de niveau supérieur et ne se réfère pas aux données réactives quelque part plusieurs niveaux ci-dessous, vous pouvez désactiver la réactivité, économisant ainsi Vue à partir d'un tas de travaux inutiles:

 const data = items.map( item => optimizeItem(item) ) function optimizeItem (item) { const itemData = { id: uid++, vote: 0 } Object.defineProperty(itemData, 'data', { //    "-" configurable: false, value: item }) return itemData } 

Source: https://habr.com/ru/post/fr483042/


All Articles