<
Media
>
Article

J'ai essayé de passer à Vue.js depuis React.js

7 min
23
/
10
/
2024

Introduction

Il y a quelques années, comme beaucoup d'autres, j'étais "hypé" par l'arrivée des hooks et les composants fonctionnels de la librairie frontend React.js. Ils proposaient une toute nouvelle manière de développer en écrivant beaucoup moins de code qu'avec les <span class="css-span">class Components</span>. J'ai véritablement accroché, et pour un bon moment.

Aujourd'hui, j'ai dû opter pour le framework Vue.js afin de répondre aux besoins d'un tout nouveau projet client. Et étant arrivé au bout de ce projet, je me suis dit que c'était l'occasion de vous proposer un retour d'expérience en tant que nouvel utilisateur de ce framework !

Alors, est-ce que cette montée en compétence a été à la hauteur de la notoriété de Vue.js ? Est-ce qu'il vaut mieux, aujourd'hui, développer du frontend en Vue qu'en React ?

C'est ce que nous allons voir !

Démarrage du projet

Boilerplate

Qui dit démarrage de projet, dit recherche d'un bon boilerplate pour nous épargner des heures, voire des jours de configuration laborieuse !

Sans avoir besoin de chercher très loin, la commande <span class="css-span">npm create vue@latest</span> répond largement à mes besoins :

<pre><code>✔ Project name: … myproject-vue
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › Playwright
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
? Add Vue DevTools 7 extension for debugging? (experimental) › No / Yes</code></pre>

Le langage Typescript est déjà pris en charge, un système de routage et un store sont proposés, et il y a même de quoi réaliser des tests unitaires et End-to-End !

Par défaut, c'est le bundler Vite qui est installé. Ce qui n'est pas pour me déplaire !😄 En effet, les builds sont rapides et, la plupart du temps le Hot Module Replacement (HMR) fonctionne bien.

Un petit <span class="css-span">npm run dev</span> pour lancer le serveur de dev local, et hop ! Ça tourne déjà dans le navigateur !

Et pour la mise en prod ? Il suffit de saisir la commande <span class="css-span">npm run build</span>, et le projet s'exporte en fichiers statiques dans un répertoire <span class="css-span">dist</span> après avoir vérifié les typages (dans le cas où l'on a activé le Typescript) :

<pre><code>vite v5.2.11 building for production...
✓ 48 modules transformed.
dist/index.html                      0.43 kB │ gzip:  0.28 kB
dist/assets/AboutView-C6Dx7pxG.css   0.09 kB │ gzip:  0.10 kB
dist/assets/index-D6pr4OYR.css       4.21 kB │ gzip:  1.30 kB
dist/assets/AboutView-CEwcYZ3g.js    0.22 kB │ gzip:  0.20 kB
dist/assets/index-CfPjtpcd.js       89.23 kB │ gzip: 35.29 kB
✓ built in 591ms</code></pre>

Architecture du projet

<pre><code>.
├── README.md
├── e2e/
├── index.html
├── package.json
├── public/
├── src/
│   ├── App.vue
│   ├── assets/
│   ├── components/
│   │   ├── HelloWorld.vue
│   │   ├── TheWelcome.vue
│   │   ├── WelcomeItem.vue
│   │   ├── __tests__/
│   │   └── icons/
│   ├── main.ts
│   ├── router/
│   │   └── index.ts
│   ├── stores/
│   │   └── counter.ts
│   └── views/
│       ├── AboutView.vue
│       └── HomeView.vue
├── tsconfig.json
└── vite.config.ts</code></pre>

Côté architecture du projet, on trouve notamment :

  • le fichier <span class="css-span">index.html</span>, avec la balise <span class="css-span">< div id="app">< /div></span> sur laquelle vient se greffer toute notre application Vue ;
  • le <span class="css-span">main.ts</span>, avec la création successive du composant App, du router et du store :

<pre><code>import './assets/main.css';

import { createApp } from 'vue';
import { createPinia } from 'pinia';

import App from './App.vue';
import router from './router';

const app = createApp(App); // composant racine

app.use(createPinia()); // store
app.use(router); // routage des pages front

app.mount('#app');</code></pre>

  • des fichiers <span class="css-span">.ts</span> purs, pour gérer le routage et le store ;
  • quelques fichiers de config et de test ;
  • ... et bien sûr les fichiers <span class="css-span">*.vue</span>, distingués en components (qui correspondent plutôt à des éléments génériques et réutilisables), et views (qui correspondent plutôt à des pages haut niveau)

En bref, l'architecture des fichiers est plutôt simple et relativement similaire à celle de React, même en ayant coché pas mal d'options dans le boilerplate.

Jusque-là, venant de React, rien de bien nouveau. C'est ensuite qu'apparaissent des différences significatives !

Architecture d'un fichier Vue

Voici un extrait de code inspiré du site officiel. Il se contente de changer la couleur du texte si l'on clique dessus et d'afficher la phrase "Le texte ci-dessus est vert", le cas échéant, mais il représente une architecture typique des fichiers <span class="css-span">*.vue</span> :

<pre><code>
<script setup>
 import { ref } from 'vue';

 const color = ref('green');

 function toggleColor() {
   color.value = color.value === 'green' ? 'blue' : 'green';
 }
</script>

<template>
 <p class="main-text" @click="toggleColor">
   Cliquez sur ce texte pour changer de couleur.
 </p>
 <p v-if="color === 'green'">Le texte ci-dessus est vert.</p>
</template>
<style scoped>
 .main-text {
   color: v-bind(color);
 }
</style>
</code></pre>

Notez le binding des évènements avec <span class="css-span">@click</span>, l'affichage conditionnel avec le <span class="css-span">v-if</span>, et le binding dans le CSS avec <span class="css-span">v-bind()</span>.

Le code est séparé en 3 parties bien distinctes :

  • script : le code de contrôle ;
  • template : la structure HTML ;
  • style : la feuille de style CSS.

Et ces trois parties ne se mélangent jamais 😮.

Cela présente plusieurs avantages que j'ai personnellement éprouvés tout au long mon expérience sur le projet client :

  • La structure HTML est claire, fixe, et dans un style très déclaratif - tout est là, même les balises affichées sous conditions ;
  • la partie logique est bien séparée de la partie affichage ;
  • on peut écrire du pur CSS en place, directement lié au composant, et sans installer de librairies tierce ;
  • malgré la séparation du style, on peut quand même insérer des variables dans le CSS.

Avec le tag <span class="css-span">scoped</span> sur la balise <span class="css-span">< style></span>, on peut même générer automatiquement des classes locales afin d'isoler sa feuille de style !

Petit inconvénient : _a priori_ il n'existe pas de polyfill CSS automatique. Il faut alors plutôt viser une librairie comme vue-emotion.
De mon point de vue, je trouve que ce genre de librairie "all-in-JS" casse un peu l'architecture que propose Vue, et de toute évidence, les propriétés CSS spécifiques aux navigateurs sont beaucoup plus rares de nos jours. La balise <span class="css-span">< style></span> de Vue est donc souvent autosuffisante.

Bref, j'ai trouvé très plaisante cette architecture tout-en-un mais avec des sections bien séparées. Cela permet de garder un code propre, mais aussi plus concis. En effet, la présence simultanée des 3 sections "logique métier / affichage / style" incite souvent à redécouper son code en plus petits modules, et donc en plus petits fichiers.

Maintenant, si on se penchait un peu plus sur l'API Vue.js elle-même ?

L'API Vue.js

Ici je ne vous ferai pas la liste exhaustive de tous les éléments de l'API Vue.js que j'ai pu rencontrer, mais seulement de certains, bien spécifiques, que j'ai trouvés assez représentatifs de la logique de Vue.

Les valeurs (re)calculées

Commençons par une opération bien connue de l'univers React : recalculer intelligemment un rendu HTML (ou une variable) suite à la mise à jour d'une donnée.

Il y a la fonction très intuitive <span class="css-span">computed()</span> qui bénéficie d'un système de mémoïsation (sorte de "cache") pour éviter de recalculer à chaque fois la valeur de sortie :

<pre><code><script setup>
  import { ref, computed } from 'vue';

  const pushedBtn = ref(1);

  const magicNumber = computed(() => {
    console.log('computed again!');
    return pushedBtn.value * 21;
  });
</script>

<template>
  <button @click="pushedBtn = 1">Button 1</button>
  <button @click="pushedBtn = 2">Button 2</button><p >Magic number: {{magicNumber}}</p>
</template></code></pre>

Ici le <span class="css-span">magicNumber</span> est calculé uniquement si la valeur de <span class="css-span">pushedBtn</span> vient à changer. Et c'est vérifiable : le message <span class="css-span">"computed again!"</span> ne s'affiche dans la console que lorsqu'un bouton différent est cliqué.

Donc, contrairement à React, pas besoin de spécifier explicitement les variables qu'il faut surveiller dans cette fonction.

Dans la même lignée, on trouve également les <span class="css-span">watch</span> et <span class="css-span">watchEffect</span> qui permettent respectivement de réagir aux changements de tout ou partie des propriétés du composant, à l'image du <span class="css-span">useEffect</span> en React :

<pre><code>
<script setup>
  import { ref, watch } from 'vue';

  const num = ref(1);
  const text = ref('a');

  const countWatchs = ref(0);

  // Attente d'un changement de 'num'
  watch([num], () => {
    countWatchs.value++;
  });
</script>
<template>
  <button @click="num += 4">Modify num</button>
  <button @click="text += 'a'">Modify text</button>
  <p>Props:</p>
  <ul>
    <li>num: {{num}}</li>
    <li>text: {{text}}</li>
  </ul>
  <hr />
  <p>Nb watchs calls: {{countWatchs}}</p>
</template>
</code></pre>

Seul un clic sur le bouton modificateur de <span class="css-span">num</span> va incrémenter le compteur de "watchs".

Le <span class="css-span">watch()</span> nous permet alors de déclencher une callback à chaque fois que certaines variables changent.

La force de cette fonction réside dans l'analyse en profondeur des modifications de variable : Vue détecte les changements même au fin fond d'un sous-objet !

La synchronisation bidirectionnelle

Déclarer et transmettre une propriété d'un composant parent à un composant enfant est une opération assez récurrente. Synchroniser cette valeur entre l'enfant et le parent l'est également, par exemple dans l'input un formulaire.

Aussi, plutôt que de gérer à la fois une propriété et une callback de mise à jour sur évènement comme ici :

<pre><code>
<!-- Child.vue -->
<script setup>
  const props = defineProps(['myModelValue']); // déclaration propriété
  const emit = defineEmits(['update:myModelValue']); // déclaration callback
</script>
<template>
  <h3>Textfield:</h3>
  <input
    :value="props.modelValue"
    @input="emit('update:myModelValue',
     ($event.target as HTMLInputElement).value)"
  />
</template>
</code></pre>

… il est possible d'utiliser à la place la macro <span class="css-span">defineModel</span> qui permet de simplifier l'écriture :

<pre><code>
<!-- Child.vue -->
<script setup>
  const myModelValue = defineModel('myModel');
</script>
<template>
  <h3>Textfield:</h3>
  <input v-model="myModelValue" />
</template>
</code></pre>

Beaucoup plus court ! 😎 D'ailleurs, n'ayant qu'un seul modèle, j'aurais même pu me dispenser de le nommer !

Et avec le parent :

<pre><code>
<!-- Parent.vue -->
<script setup>
  import { ref } from 'vue';
  import Child from './Child.vue';

  const data = ref('my default value');
</script>

<template>
  <div>Parent value: {{data}}</div>
  <Child v-model:my-model="data"></Child>
</template>
</code></pre>

La boucle for

Enchaîner sur les <span class="css-span">v-for</span> après avoir vu les <span class="css-span">v-model</span> m'a fait réaliser que Vue.js commençait à introduire pas mal de "magie" à travers du code implicite :

<pre><code>
<script setup>
  const commonGitActions = ['Pull', 'Commit', 'Push', 'Merge', 'Rebase'];
</script>

<template>
  <h1>Actions git communes</h1>
  <ul>
    <!-- Boucle "for" intégrée -->
    <li v-for="action in commonGitActions">{{action}}</li>
  </ul>
</template>
</code></pre>

Et le rendu :

Comme on pouvait s'y attendre, l'instruction <span class="css-span">v-for</span> permet donc de répéter automatiquement une partie d'un patron HTML (ici la balise <span class="css-span">< li></span>) pour chaque élément d'un itérable.

Côté React, il aurait fallu utiliser du JSX pour construire soi-même chaque élément, rendant le code moins lisible au fur et à mesure que le composant grandit :

<pre><code>
import React from 'react';

const commonGitActions = ['Pull', 'Commit', 'Push', 'Merge', 'Rebase'];

export function App(props) {
  return (
    <>
      <h1>Actions git communes</h1>
      <ul>
        {commonGitActions.map((action) => (
          <li key={action}>{action}</li>
        ))}
      </ul>
    </>
  );
}
</code></pre>

Personnellement, j'ai une préférence pour la structure de Vue en termes de propreté de code, tant qu'il n'y a pas besoin de déboguer. 😅

Et d'ailleurs, puisqu'on parle de déboguer, qu'en est-il des outils de l'écosystème Vue ?

Les outils de dev

Voici 3 outils qui ont retenu mon attention dans le développement de mon projet.

Extension VSCode : Vue Official

Je commence par une évidence, mais oui, il y a bien une extension pour VSCode Vue (et pour d'autres IDEs) qui ajoute la coloration syntaxique, autocomplétion, snippets, etc. Un indispensable !

Toutefois, j'ai constaté quelques instabilités sur la coloration et l'autocomplétion, qui étaient parfois un peu capricieuses 😕, là où j'ai pu apprécier une plus grande stabilité côté React.

Vue.js devtools

À l'image du plugin de navigateur <span class="css-span">React Developer Tools</span>, il existe le plugin <span class="css-span">Vue.js devtools</span>, qui, je dois l'avouer, est déjà très bien fourni :

On y retrouve 4 onglets :

  • Components, où on l'on peut observer, mais également modifier les états des composants ;
  • Timeline, qui permet d'enregistrer les évènements et les temps de rendu des composants, ce qui correspond en fait à une version de l'onglet "Performances" du navigateur mais focalisée sur Vue ;
  • Pinia, qui permet d'observer et modifier directement les états du store standard 😯, une intégration déjà toute prête que j'ai trouvée particulièrement bienvenue ;
  • Routes, où l'on peut lister les différentes routes et leur activité - c'est un onglet que j'ai trouvé un peu gadget sur mon projet de taille modérée (d'autant plus que les informations sont un peu redondantes avec celles de l'onglet "Components"), mais qui peut s'avérer bien utile sur un routage complexe.

Bref, pour du débogage, il y a tout ce qu'il faut et même plus !

Vuetify

Quasiment sans surprise (mais avec du mérite tout de même !), il existe aussi une librairie UI pour Vue qui implémente les Material Design de Google et fournit également une liste d'icônes standards : Vuetify.

Cela permet de gagner beaucoup de temps sur des démarrages de projet, ou sur des projets qui ne nécessitent pas de personnalisation graphique trop poussée.

Mais comme toujours, je recommande de garder un œil sur les performances de rendu avec ce genre de librairie haut niveau. La capacité de configuration d'une librairie se paie souvent ailleurs !

Conclusion

Que dire de cette expérience de migration de React vers Vue ?

Tout d'abord, d'un point de vue du code, par rapport à React je dirais que la lib Vue est :

  • plus structurée
  • plus déclarative
  • plus concise

Toutefois, grâce à son code qui s'écrit plutôt en JSX, je trouve que React reste beaucoup plus interopérable, plus programmatique et plus explicite que Vue, et avec une meilleure stabilité du linter.

Côté environnement de développement et communauté, Vue a pour moi toutes les cartes en main pour assurer des développements efficaces jusqu'à la mise en prod.

Alors est-ce que cette montée en compétence sur Vue a été à la hauteur de sa notoriété ? Je dirais que oui. J'ai trouvé la courbe d'apprentissage efficace, et je continuerai de développer avec Vue si l'occasion se présente.

Enfin, est-ce qu'aujourd'hui il vaut mieux développer du frontend en Vue qu'en React ?

D'un point de vue totalement personnel, je pense que non. Même si Vue et React ont chacun des cas d'application un peu différents, je préfère me fier à un système de typage fiable et à un code plus souple avec React. Mais peut-être que les prochaines versions de Vue et leurs outils me feront changer d'avis ?

Et vous, quels sont vos retours d'expérience ?

No items found.
ça t’a plu ?
Partage ce contenu
Jean-Noël

Jean-Noël aime sa guitare, sa PS4 (quoi de mieux que de réaliser un super combo sur sa manette) et coder... mais de préférence en Typescript !

Et oui, le Typescript, c’est son dada ! Il y a une très forte interopérabilité, le typage apporte de la rigueur au JS, et tout fonctionne très bien, très vite ! En revanche, quand il s’agit d’algorithmes, il préfère le Rust ! C’est le seul langage pour lequel, lorsqu’il a réussi à compiler, il peut respirer en criant : « MON CODE EST SUUUUR !!! ».

On ne sait pas si c’est pour pouvoir rédiger des tas d’articles pour le blog YOUNUP mais il nous a confié rêver de pouvoir regénérer ses cellules pour une jeunesse et un savoir sans limite. Un mix entre le film « Bienvenue à Gattaca » et les écrits de « Laurent Alexandre » ?