Vue.js, alternative à Angular et React

# Frontend# JavaScript# VueJs
écrit par Jérémy Génard publié le jeudi 2 mars 2017

Dans le monde du JavaScript, on entend parler de React, d'AngularJS mais il y a aussi quelques alternatives. Nous allons voir ensemble l'une d'entre elles : Vue.js

Introduction

Dans cet article, je vais essayer de vous présenter quelques éléments qui vous permettront de construire une petite application avec Vue.js. Pourquoi j'ai choisi de parler de ce framework ? C'est un concurrent de React et d'Angular qui commence à faire parler de lui en dehors de la communauté des développeurs Frontend et c'est une des raisons de mon intérêt. La deuxième est qu'il se veut comme un outil plus simple et plus rapide à prendre en main et c'est l'objet de l'article d'aujourd'hui. Enfin, il propose une approche similaire à React pour la construction des composants sauf qu'il n'est pas nécessaire de tout faire en JS, notamment au niveau du templating et c'est aussi un des sujets qui me préoccupe.

Prérequis

Pour ce guide, nous aurons besoin d'outils pour nous aider dans notre découverte de Vue.js
  • Un IDE, mon choix s'est porté sur Visual Studio Code avec quelques extensions :
    • npm, ESLint, vue, Vue Components
  • Node.js et npm

Installation

Pour se faciliter la tâche, Vue.js propose une CLI pour démarrer rapidement un projet : https://vuejs.org/v2/guide/installation.html#CLI Il nous suffit d'ouvrir la vue Terminal (ctrl+ù) de VS Code et saisir les commandes suivantes :
  1. npm install --global vue-cli
    • Installe le CLI de Vue.js
  2. vue init webpack nom-du-projet
    • Initialise le template Webpack
  3. cd nom-du-projet
  4. npm install
    • Installe les dépendances du projet tel que définies dans le fichie package.json
  5. npm run dev
    • Build l'application et démarre un serveur HTTP
A partir de là, vous devriez avoir une page ouverte dans votre navigateur avec le logo de Vue.js comme celle ci-dessous. [caption id="attachment_373" align="aligncenter" width="453"]Page de démarrage Vue.js Page de démarrage Vue.js[/caption] Vous pouvez laisser votre serveur tourner pendant que vous développez ça permettra de voir directement les erreurs d'ESLint dans le terminal de VS Code.

L'application

Pour comprendre les bases de ce que propose Vue.js, nous allons faire une petite application qui va lister des posts de Reddit. Je vais essayer de vous détailler au maximum chacune des parties du code. N'hésitez pas à nous laisser des questions et des précisions dans les commentaires de l'article.

Architecture

Le CLI nous a créé la structure suivante :
  • build -> Utilitaires pour builder/lancer l'application
  • config -> Configuration des environnements
  • dist -> Application en sortie de build
  • node_modules -> Modules node.js
  • src -> Sources de l'application
    • assets -> Fichiers d'assets de l'application
    • components -> Composants Vue
  • static -> Fichiers d'assets statiques qui ne sont pas liés au fonctionnement de l'application
  • test -> Sources des tests de l'application

Les composants

Nous aurons besoin de deux composants : Subreddit et Post

Subreddit

Un subreddit est un peu comme une catégorie de posts autour d'un sujet. Nous aurons donc une dépendance vers le composant Post. Nous allons créer ce composant dans le dossier components : Subreddit.vue

Template

Tout d'abord construisons le template HTML :
<template id="subreddit">
  <div class="subreddit">
    <h2>{{ name | uppercase }}</h2>
    <ul class="item-list">
      <li v-for="obj in posts">
        <post v-bind:item="obj"></post>
      </li>
    </ul>
  </div>
</template>
On observe déjà trois choses dans ce morceau de code :
  1. v-for permet de boucler sur une liste de données et de répéter automatiquement des éléments HTML
  2. v-bind permet de passer des données au composant
  3. <post></post> représente notre deuxième composant Post

Script

Intéressons nous au comportement de notre Subreddit :
<script>
import Post from './Post';

export default {
  name: 'subreddit',
  components: {
    Post,
  },
  props: {
    name: {
      type: String,
      required: true,
    },
    nbPosts: {
      type: Number,
      required: false,
      default: 5,
    },
  },
  data() {
    return {
      posts: [],
    };
  },
  created() {
    this.$http.get(`https://www.reddit.com/r/${this.name}/top.json?limit=${this.nbPosts}`)
    .then(function manageResponse(resp) {
      let responseData = resp.data;
      if (typeof responseData === 'string') {
        responseData = JSON.parse(responseData);
      }
      this.posts = responseData.data.children;
    });
  },
};
</script>
Plusieurs points d'intérêts dans le code ci-dessus :
  1. L'instruction import Post from './Post' nous permet d'utiliser le composant Post que nous créerons plus tard
  2. On construit le module associé à notre composant avec export default {}
  3. On définit les propriétés :
    • name correspond au nom qui sera utilisé dans le template HTML, ici : <subreddit></subreddit>
    • components permet de déclarer les composants utilisés
    • La propriété props nous donne la possibilité d'ajouter les attributs suivants
      • Le nom du Subreddit avec name
      • Le nombre de posts que nous souhaitons afficher avec nbPosts
  4. data() permet de passer des données
  5. created() est déclenchée lorsque le composant est créé et nous appelons l'API de Reddit pour récupérer les derniers posts du subreddit en question

Style

Il n'est pas nécessaire d'expliquer ce que nous faisons dans cette section du code si ce n'est le mot-clef scoped. Ça permet d'éviter la surcharge des classes CSS du composant par d'autres.
<style scoped>
.subreddit {
  flex: 0 0 33%;
  min-width: 400px;
  padding: 20px 42px;
}

.subreddit h2 {
  font-size: 18px;
  margin-bottom: 10px;
}

.subreddit .item-list {
  border-top: 1px solid #bec9d0;
  padding-top: 20px;
  list-style: none;
}

.subreddit .item-list li {
  margin-bottom: 17px;
}
</style>
 

Post

Deuxième composant de notre petite application, il n'a aucune dépendance extérieure si ce n'est un placeholder lorsque l'image du post est indisponible. Comme pour le composant précédent, nous allons créer un nouveau fichier dans le dossier components : Post.vue
Template
Commençons par regarder le code HTML du composant :
<template id="post">
  <div class="post">
    <a :href="item.data.url" target="_blank" class="thumbnail" :style="getImageBackgroundCSS(item.data.thumbnail)"></a>
    <div class="details">
      <a :href="item.data.url" :title="item.data.title" target="_blank" class="title">{{ item.data.title | truncate}}</a>
      <div class="action-buttons">
        <a v-bind:href="url" title="Vote">
          <i class="material-icons">thumbs_up_down</i>{{item.data.score}}
        </a>
        <a v-bind:href="url" title="Go to discussion">
          <i class="material-icons">forum</i>{{item.data.num_comments}}
        </a>
      </div>
    </div>
  </div>
</template>
Ici, on peut voir qu'il y a beaucoup de petites choses :
  1. :href="item.data.url" et :title="item.data.title" permettent de récupérer et de positionner des données au niveau de nos attributs
  2. On peut aussi appelé des méthodes de notre composant depuis le template, exemple :style="getImageBackgroundCSS(item.data.thumbnail)"
  3. L'instruction {{item.data.score}} permet d'écrire directement la valeur d'une propriété d'un objet
    • On peut également utiliser des filtres et/ou des fonctions sur les valeurs, exemple : {{ item.data.title | truncate}}

Script

Il a peu de nouvelles choses ici mais étudions quand même le code suivant.
<script>
const placeholder = require('../assets/logo.png');

export default {
  name: 'post',
  props: ['item'],
  data() {
    return {
      url: `http://reddit.com${this.item.data.permalink}`,
    };
  },
  methods: {
    getImageBackgroundCSS: function getImageBackgroundCSS(img) {
      if (img && img !== 'self' && img !== 'nsfw') {
        return `background-image: url('${img}')`;
      }
      return `background-image: url(${placeholder})`;
    },
  },
};
</script>
  1. On déclare le placeholder de l'image du post avec const placeholder = require('../assets/logo.png')
    • Ce n'est pas Vue.js qui nous impose le require mais Webpack du fait de l'url relative
  2. On ajoute la méthode getImageBackgroundCSS qui est utilisée dans le template pour injecter l'image du post.

Style

Dernier élément de notre composant : le CSS. Comme pour Subreddit.vue, le style est scoped et on s'appuiera sur les flexbox pour construire le visuel. Je vous laisse étudier le CSS ci-dessous, il n'y a pas grand chose à dire dessus.
<style scoped>
.post {
  display: flex;
}

.post .thumbnail {
  display: block;
  flex: 0 0 60px;
  height: 60px;
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
  margin-right: 10px;
  border-radius: 4px;
  margin-right: 12px;
}

.post .details {
  display: flex;
  flex-direction: column;
}

.post .details .title {
  font-size: 15px;
  margin-bottom: 3px;
  color: #04477b;
}

.post .details .title:visited {
  color: purple;
}

.post .details .action-buttons a {
  font-size: 11px;
  margin-right: 4px;
  display: inline-block;
  color: #666;
}

.post .details .action-buttons i {
  font-size: 10px;
  margin-right: 1px;
}
</style>

Le conteneur principal

Maintenant que nous disposons de nos composants principaux, il nous faut un conteneur. Nous créons le fichier App.vue à la racine du dossier src. Vous avez deviné avec l'extension, c'est aussi un composant. Nous construisons donc le template suivant :
<template>
  <div id="app">
    <div class="container">
      <subreddit name="food" v-bind:nbPosts="3"></subreddit>
      <subreddit name="funny"></subreddit>
      <subreddit name="france" v-bind:nbPosts="3"></subreddit>
      <subreddit name="nba"></subreddit>
    </div>
  </div>
</template>
Nous pouvons voir que nous réutilisons le composant Subreddit en utilisant la propriété name pour connaitre le subreddit que nous souhaitons afficher et nous pouvons, si on le souhaite, utiliser la propriété nbPosts. Le script du conteneur est très simple :
<script>
import Subreddit from './components/Subreddit';

export default {
  name: 'app',
  components: {
    Subreddit,
  },
};
</script>
On déclare l'utilisation du composant Subreddit et il nous reste plus qu'à écrire le CSS suivant :
<style>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

a {
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

html {
  font: normal 16px sans-serif;
  color: #333;
  background-color: #f9f9f9;
}

.container {
  padding: 27px 20px;
  margin: 30px auto 50px;
  max-width: 1250px;
  display: flex;
  flex-wrap: wrap;
  flex-direction: row;
  background-color: #fff;
  box-shadow: 0 0 1px #ccc;
}

#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Configuration, filtres et outils

Il nous manque le point d'entrée de l'application ainsi que deux filtres (uppercase et truncate que j'évoquais plus haut). Il faut deux éléments, un fichier index.html à la racine de notre projet (en dehors du dossier src). Rien de bien compliqué comme vous pouvez le voir ci-dessous. En effet, il nous suffit d'ajouter une div qui va contenir notre application.
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Vuejs - Reddit demo</title>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
Nous avons également besoin d'un morceau de JavaScript pour initialiser Vue.js et ses dépendances. Il faut donc créer un fichier main.js à la racine de src s'il n'a pas déjà été ajouté par le CLI de Vue.
import Vue from 'vue';
import VueResource from 'vue-resource';
import App from './App';

Vue.use(VueResource);

Vue.filter('uppercase', value => value.toUpperCase());

Vue.filter('truncate', (value) => {
  const length = 40;

  if (value.length <= length) {
    return value;
  }
  return `${value.substring(0, length)}...`;
});

/* eslint-disable no-new */
new Vue({
  el: '#app',
  template: '<App/>',
  components: { App },
});
Comme vous pouvez le voir, nous avons 3 instructions import :
  1. import Vue from 'vue' -> Nous avons bien évidemment besoin de Vue.js
  2. import App from './App' -> Nous devons importer notre application pour l'utiliser en tant que composant
  3. import VueResource from 'vue-resource' -> Cette dépendance nous permet d'effectuer des requêtes à des API, notamment celle de Reddit. C'est le client HTTP de Vue.js.
    • Lien du projet : https://github.com/pagekit/vue-resource
    • Remarque : S'il n'est pas déjà installé, il faudra jouer la commande npm install vue-resource
    • L'import ne suffit pas, il faut indiquer à Vue qu'il doit s'en servir, d'où le code Vue.use(VueResource)
Une fois que nous avons géré nos dépendances, on doit instancier notre application avec le code ci-dessous. On indique alors l'élément cible qui va la porter, un simple template et le composant à injecter.
/* eslint-disable no-new */
new Vue({
  el: '#app',
  template: '<App/>',
  components: { App },
});
Enfin, il nous reste à écrire les deux filtres utilisés par nos composants : uppercase et truncate. Il faut les déclarer en utilisant la méthode filter() de Vue.
Vue.filter('uppercase', value => value.toUpperCase());

Vue.filter('truncate', (value) => {
  const length = 40;
  if (value.length <= length) {
    return value;
  }
  return `${value.substring(0, length)}...`;
});

Lancement de l'application

Maintenant que nous avons l'ensemble de notre petite application, il est temps de tester que tout fonctionne. Le CLI a mis en place tout l'environnement nécessaire pour builder l'application avec Webpack. Il suffit d'utiliser la commande que nous évoquions en début d'article : npm run dev Finalement et si tout s'est bien passé, vous devriez avoir le résultat suivant : [caption id="attachment_377" align="aligncenter" width="469"] Application finale avec 4 subreddits[/caption]

Conclusion

Finalement que retenir ? Vue.js est un framework très simple à utiliser. A mon sens, j'ai trouvé qu'il était plus facile à prendre en main que React et notamment dans la construction des composants, d'autant plus qu'on n'est pas noyé par l'ensemble des dépendances. Il faut ajouter aussi que Vue.js peut être ajouter sur n'importe quel projet en incluant directement le framework dans ses pages avec <script src="https://unpkg.com/vue"></script>. La vraie question est de savoir est-ce qu'il est production ready. Et là, je n'aurais pas assez d'éléments pour vous répondre. Le framework est encore jeune, il n'a pas la communauté d'un React ou d'un Angular et il est donc probable qu'il lui manque des composants essentiels à vos applications. Pour aller plus loin, vous pouvez jeter un oeil à Vuex, une librairie qui s'occupe du state management, avec cet excellent article disponible sur Medium : https://medium.com/@bradfmd/vue-vuex-getting-started-f78c03d9f65#.wc18lu5uf