Vue 3, les nouveautés et les améliorations

Le 19 avril dernier, Evan You annonçait la sortie de la version beta de Vue 3.0, avec comme introduction "Great things for TS users coming in Vue 3", quelles sont les nouveautés de cette release ?

Vue 3, les nouveautés et les améliorations

Ceux qui me connaissent savent à quel point je suis attaché à l’écosystème Vue.js et ce depuis sa date de sortie.Venant de React, j’ai tout de suite accroché à la philosophie Vue.js et j’ai eu la chance de contribuer à de chouettes projets utilisant cette techno. Cependant j’ai parfois pu en rencontrer les limites, notamment en terme de maintenabilité, de scalabilité ou de performance. Ces limitations m’ont même récemment conduit à me rediriger vers React, que j’avais laissé depuis quelques mois et j’y ai trouvé ce qui me manquait sur Vue 2, notamment pour les hooks.

Alors, quelles sont les nouveautés de Vue 3 ?

Performance

Une des premières avancées de Vue 3 concerne la performance, et c’est loin d’être négligeable car il est annoncé une amélioration de 1.3 à 2 fois plus performant sur un re-rendering de composant (Basé sur des scénarios typiques par rapport à la version courante) et de 2 à 3 fois plus rapide en SSR.

L’implémentation du virtual dom qui a été entièrement réécrite et l’optimisation globale du code ont permis de réduire le poids du bundle de moitié.

Tree-shaking support

Dorénavant les features optionnelles, telles que transition, v-model, v-show, keep-alive … ne seront embarquées dans votre bundle que si elles sont utilisées.

Pour cela, au moment du build, le compilateur de Vue détectera les directives qui ont été utilisées pour les intégrer à la fonction de rendering des templates.

Composition API

C’est sans doute la feature la plus attendue de cette nouvelle version de Vue. En effet, si la syntaxe des composants Vue reste la même que celle de Vue 2, à savoir l’utilisation des propriétés data, computed, methods, lifecycle, il sera désormais possible de penser totalement différemment la manière dont les composants sont créés grâce au composition API.

Le composition API a été créé pour répondre au problème de lisibilité et de maintenabilité du code des composants.

Pourquoi composition API ?

Maintenabilité

Ceux d’entre vous qui sont déjà familiers avec Vue 2 se sont déjà sûrement retrouvés dans la situation ou plus un composant vue prend de l’ampleur, plus il devient difficile à maintenir.

Prenons l’exemple simple du compteur ci-dessous.

<template>
  <div>
    <button @click="increment">Increment</button>
    <p>Count : {{ count }}</p>
    <p v-if="isMaxReached">Sorry: You can add more than 10</p>
  </div>
</template>

<script>
  export default {
    props: {
      max: {
        default: null,
        type: Number,
      },
    },
    data() {
      return {
        count: 0,
      }
    },
    computed: {
      isMaxReached() {
        return this.max && this.count >= this.max
      },
    },
    methods: {
      increment() {
        if (!this.isMaxReached) this.count++
      },
    },
  }
</script>

Notre compteur qui est pourtant un composant très simple, fait appel à 4 options (Props, data, computed, methods) pour interagir avec la simple valeur de count et en gérer l’affichage dans notre template. Il nous faut alors passer par une méthode pour updater la valeur de count, puis par une computed pour savoir si elle dépasse le max qui lui est déterminé par une props. Imaginez maintenant que notre composant ait besoin de plus de données avec lesquelles interagir…C’est ce qu’Evan You a voulu optimiser en créant le composition API.

Nous allons maintenant recréer ce même composant à l’aide du composition API et en analyser la structure.

<template>
  <div>
    <button @click="increment">Increment</button>
    <p>Count : {{ count }}</p>
    <p v-if="isMaxReached">Sorry: You can't add more than 10</p>
  </div>
</template>

<script>
  import { ref, computed } from 'vue'

  export default {
    props: {
      max: {
        default: null,
        type: Number,
      },
    },
    setup(props) {
      const { max } = props
      const count = ref(0)
      const isMaxReached = computed(() => max && count.value >= max)
      const increment = () => {
        if (!isMaxReached.value) count.value++
      }

      return {
        count,
        isMaxReached,
        increment,
      }
    },
  }
</script>

La première chose que vous avez sans doute remarquée,c’est la présence de la méthode setup.

Bien vu ! C’est cette méthode qui permet de retourner tout ce dont nous aurons besoin pour assurer le rendering de notre template, à savoir nos données et nos fonctions count, isMaxReached, increment.

Pour expliquer rapidement le fonctionnement, vous remarquerez que notre méthode setup prend en argument les props de notre composant, nous pouvons donc à l’aide de la déstructuration d’objet passer uniquement la props dont nous avons besoin, dans notre cas max.

La première ligne de notre méthode déclare la propriété réactive count à l’aide de la fonction ref de composition API. pour les utilisateurs de React, cette syntaxe devrait vous être familière.

Enfin nous avons notre computed isMaxReached puis notre fonction increment. Deux petites choses à noter pour cette partie, la première est que nous devons utiliser la fonction computed pour retourner une propriété calculée à partir d’une propriété réactive telle que ref. La seconde est que pour accéder à la valeur de notre propriété il est indispensable de spécifier explicitement que nous voulons la value de notre référence. Dans notre cas pour obtenir la valeur de count il nous faudra appeler count.value, ce qui ne sera en revanche pas nécessaire dans le template.

Vous remarquerez que cette syntaxe est beaucoup plus claire est plus facile à maintenir et répond à la problématique que nous rencontrions sur Vue 2. Grâce au composition API il est possible d’organiser son code en fonction des besoins de notre composant et non en fonction de la syntaxe de Vue.

Scalabilité et réusabilité

Un autre point faible de Vue 2 est sa capacité à réutiliser des parties de code ou des logiques. Pour cela, Vue propose actuellement deux possibilités.

Les mixins, mais ces dernières ne permettent pas de connaître réellement ce qu’elles ajoutent à notre composant. Elles peuvent également créer des conflits avec les namings déjà utilisés dans notre composant.

Les scoped slots, qui résolvent les problèmes liés aux mixins mais qui demandent beaucoup de configurations et qui ne sont accessibles que via le template, ce qui rend notre composant moins flexible et aussi moins performant.

Avec composition API il est désormais possible de décentraliser la logique de nos composants et de les réutiliser où nous le souhaitons.

Reprenons notre exemple.

Nous allons créer un fichier counter.js qui centralisera la logique de notre compteur.

import { ref, computed } from 'vue'

export default ({ max }) => {
  const count = ref(0)
  const isMaxReached = computed(() => max && count.value >= max)
  const increment = () => {
    if (!isMaxReached.value) count.value++
  }

  return {
    count,
    isMaxReached,
    increment,
  }
}

Que l’on pourra réutiliser de cette manière dans notre composant

<template>
  <div>
    <button @click="increment">Increment</button>
    <p>Count : {{ count }}</p>
    <p v-if="isMaxReached">Sorry: You can't add more than 10</p>
  </div>
</template>

<script>
  import useCounter from './use/counter'

  export default {
    props: {
      max: {
        default: null,
        type: Number,
      },
    },
    setup(props) {
      const { count, isMaxReached, increment } = useCounter(props)

      return {
        count,
        isMaxReached,
        increment,
      }
    },
  }
</script>

Beaucoup plus clair comme ça non ? :)De cette manière, nous sommes pas restreints au template ou au composant et nous pouvons également bénéficier de l’autocompletion car counter est juste une fonction retournant un objet.

Fragment, Teleport, Suspense, les fonctionnalités inspirées de l’écosystème React

Fragment

Aujourd’hui Vue est limité à un seul node comme root element dans notre template. Ce qui peut parfois être limitant, ce ne sera plus le cas avec Vue 3. Il sera également possible de créer des fonctions custom de rendering.

Teleport

D’abord appelé Portal, Teleport permet de téléporter du HTML à un autre endroit dans notre DOM.

<!-- Dans un composant Vue -->
<VueComponent>
  <Teleport target="#teleport-target">
    <span>Je vais me téléporter</span>
  </Teleport>
</VueComponent>

<!-- Disponible dans le DOM -->
<div id="teleport-target"></div>

Ainsi notre élement deviendra :

<div id="teleport-target">
  <span>Je vais me téléporter</span>
</div>

<!-- Disponible dans le DOM -->
<div id="teleport-target"></div>

Suspense

La fonctionnalité Suspense de Vue 3 permet de faire du Lazy Loading de nos composants, c’est donc un composant spécial qui rendra un composant fallback jusqu’à ce notre composant par défaut soit totalement chargé.

<Suspense>
  <template #default>
    <WithFetch />
  </template>
  <template #fallback>
    <div>Loading...</div>
  </template>
</Suspense>

Imaginons que notre composant WithFetch fasse un appel asynchrone vers un API, alors notre code ci-dessus montrera le message Loading en attendant la réponse de notre fetch.

Amélioration du support TypeScript

Une amélioration très importante concerne l’amélioration du support TypeScript, notamment car la codebase a été entièrement écrite en TypeScript.

Comment migrer de Vue 2 vers Vue 3 ?

La migration de Vue 2 vers Vue 3 sera simplifiée car comme nous l’avons vu précédemment, toutes les fonctionnalités de Vue 2 continueront de fonctionner. La syntax générale restant la même, il vous restera juste à migrer petit à petit vos composants vers la syntaxe Composition API au fil de l’eau.

Et pour Nuxt ?

Les équipes de Nuxt ont déjà développé un prototype fonctionnel de Nuxt utilisant Vue 3 et le composition API.

Conclusion

Après avoir joué un peu avec cette nouvelle version, je dois avouer que je suis très enthousiaste à l’idée de l’utiliser sur un projet d’envergure. Vue 3 conserve sa philosophie, à laquelle j’ai toujours adhéré et offre dorénavant la flexibilité qu’il me manquait dans Vue 2 et que j’appréciais dans React.

Hâte de le tester en conditions réelles :)