Publié le 13/10/2020 Par Bastien Remond

Cet article a pour objectif d’établir des bases de compréhension ainsi que de petites idées d’architecture pour vous lancer sur Vue avec Typescript (ou plutôt Vue.Ts).

Lors de mon alternance et au cours de mes études, j’ai majoritairement travaillé sous Angular 2+. En conséquence, une grande partie de mon travail s’effectuait sur Typescript que je me suis mis à apprécier bien plus que Javascript. Ma première expérience professionnelle à la SNCF reposait sur du from-scratch en Vue. Avec l’accord du responsable technique, je me suis alors lancé et j’ai récemment pu tester le support mis en valeur de Typescript avec Vue. Mais comment correctement appréhender Vue avec Typescript ?  

Cet article a pour objectif d’établir des bases de compréhension ainsi que de petites idées d’architecture pour vous lancer sur Vue avec Typescript (ou plutôt Vue.Ts). 

Pourquoi Typescript ? 

Vous n’avez toujours pas essayé Typescript ? Il n’est jamais trop tard et les raisons de vous y mettre ne manquent pas : 

  • C’est open source ! 
  • La grande lisibilité et la bonne compréhension du code 
  • La possibilité de faire de « l’orienté objet » bien plus facilement 
  • Les types bien mieux fais qu’avec Javascript, et le support des types génériques 
  • Le paramétrage du compilateur fourni très simple et assez complet (le fait de pouvoir choisir sa version de Javascript) 
  • La documentation moins contraignante à faire pour le développeur (par exemple avec Typedoc
  • De plus, le langage est vraiment très abordable pour un débutant en orienté objet ! 

L’un des principaux avantages est que l’on gagne en cohérence et en structure vis-à-vis de Vue.js classique qui, avec ses concepts de base, cache un peu de programmation orienté objet. Les classes et autres fonctionnalités proposées par le Typescript permettent alors de les exploiter de manière plus transparente. 

Partir sur de bonnes bases sur Vue avec Typescript 

Tout d’abord, si vous n’avez pas la Vue-CLI : npm i -g @vue/cli, pas de problème. L’installation est assez simple car la Vue-CLI prend désormais en charge l’initialisation de projets sous Typescript. Il vous suffira donc de sélectionner le paramétrage manuel du projet lors de la commande : Vue create [mon_projet].  

Puis, pour initialiser Typescript, Vuex et le routeur qui seront nécessaires pour notre petit projet, vous devez les cocher via la barre espace. 

Bien évidemment, nous allons exploiter le langage au mieux en créant des composants Vue basés sur un système de classes. Nous allons également activer le support de babel pour Typescript afin de pouvoir avoir accès aux fonctionnalités telles que la gestion de la compatibilité des navigateurs et la détection automatique des polyfills requis pour le projet. 

Pour le reste, c’est à vous de voir ! Je recommande tout de même d’ajouter TSLint pour des raisons évidentes de propreté mais ce n’est pas forcément nécessaire ici. 

Analyse d’un composant : les différences 

Comparons maintenant la structure d’un fichier Vue.js et d’un fichier Vue.ts. Première différence : quand la version classique déclare le composant via un export d’objets javascript, la version TS exporte une classe via le décorateur @component. Composant qui récupère le nom de la classe déclarée et classe qui étend l’object Vue importé plus haut. 

<script>
 export default {
 name: 'HelloWorld',
 }
</script>
<script lang="ts">

 import {Component,Vue} from 'vue-property-decorator';
 import {namespace} from 'vuex-class';

 @Component
 export default class HelloWorld extends Vue {
 }
</script>

Maintenant, occupons-nous des déclarations.  Qu’il s’agisse des propriétés ou des données, il est toujours nécessaire d’implémenter des propriétés de l’objet javascript exporté. En TS, étant dans une classe, la déclaration ne se fait pas de la même manière. En effet, pour une propriété héritée, on utilisera le décorateur adapté @Prop. Pour les données, une simple déclaration d’attribut suffit. 

export default { 

 name: 'HelloWorld', 

 props: { 

 msg: String 

 }, 

 data: function () { 

 return {hello: String} 

 }, 
export default class HelloWorld extends Vue { 

 @Prop() private msg!: string; 

 private _data = 'Hello'; 

} 

Ici, vous pouvez déjà noter que vous gagnez en lisibilité et que la notion d’accès est désormais présente. De même, les types utilisés ne sont pas les types Javascript mais bien Typescript avec tout ce que cela implique : utilisation des types de classes, d’interfaces, d’énumérateurs, des fichiers de définitions TS, etc. 

Pour la déclaration des méthodes, c’est la même chose. On ne déclare plus de fonction dans la propriété ‘‘methods’’ de notre objet composant mais, à l’instar des données, directement dans la classe. 

methods: { 

 helloThere: function () { 

 return 'General Kenobi'; 

 }, 

}, 
public helloThere(): string { 

 return 'General Kenobi'; 

} 

Une subtilité importante : le cycle de vie 

L’un des points les plus importants, si l’on veut correctement appréhender Vue avec Typescript, repose sur les hooks. Ceux-ci s’appellent comme des méthodes classiques. Il est possible d’y accéder directement dans la classe un peu comme angular. De ce côté-là, rien de complexe. 

En revanche, où se situe exactement le constructeur dans l’exécution du cycle de vie, comment s’initialise un attribut et qu’est-ce ce que cela change par rapport à l’usage classique de la donnée ? 

Lorsque je déclare une donnée dans l’objet data, Vue va mettre cette donnée dans un “object Observer” qui va la rendre réactive. Ce n’est pas le cas lorsque j’initialise directement l’attribut dans ma classe/composant Vue.Ts ! En effet, celle-ci sera disponible seulement dans le constructeur de ma classe, ne sera pas réactive et sera réinitialisée par un objet observateur vide au passage du cycle de vie created.  

Voyez plutôt : 

private _data = 'Hello'; 

 

constructor() { 

 super(); 

 console.log('Constructed ' + this._data); 

} 

 

public beforeCreate() { 

 console.log('BeforeCreated ' + this._data); 

} 

 

public created() { 

 console.log('Created '); 

 console.log(this._data); 

} 

Le système de décorateur fonctionne pour tout 

Étant donné que vous n’allez pas avoir accès directement à certaines méthodes telles que Watch, vous pourriez alors être tenté de passer par l’objet Vue directement. Mais vous n’avez théoriquement pas besoin d’utiliser l’objet Vue pour utiliser ses fonctionnalités. Il existe un décorateur pour chacune d’entre elles !  

Exemple avec Watch. On passe de… 

watch: {
 data: function (newValue, oldValue) {
 console.log('Do something when attribute data change')
 }
},

… à : 

@Watch('data')
private onDataChange(newValue: string, oldValue: string): void {
 console.log('Do something on attribute data change');
}

Voici une vue d’ensemble afin de pouvoir comparer de manière plus globale des composants Javascript et Typescript :  

<script>
 export default {
 name: 'HelloWorld',
 props: {
 msg: String,
 },
 data: function () {
 return {hello: String}
 },
 methods: {
 helloThere: 
 function () {
 return 'General Kenobi';
 },
 watch: {
 hello: 
 function (newValue,oldValue) {
 console.log(
 'Do something when 
 attribute hello change')
 }
 },
 }
</script>
<script lang="ts">

 import {Component,Vue} from 
'vue-property-decorator';
 import {namespace} from 'vuex-class';

 @Component
 export default class HelloWorld extends Vue {

 @Prop() private msg!: string;
 private hello = 'Hello';

 public helloThere(): string {
 return 'General Kenobi';
 }

 @Watch('hello')
 private onHelloChange(
 newValue: string, oldValue: string): void{
 console.log(
 'Do something on attribute hello change');
 }
 }
</script>

Idée d’architecture et utilisation du plugin vuex-class 

Maintenant que nos composants sont propres, voici une idée d’application pour séparer avec le plus de propreté, la communication et le modèle. Pour les besoins de cette démo, et si vous voulez la reproduire, installez Axios : npm i –save axios. 

Avant tout, nous allons utiliser le plugin vuex-class dont l’installation se fait avec la commande : 

npm install –save vuex-class 

Lorsque c’est fait, nous allons initialiser deux services dans le store, un service qui sera chargé de faire une requête à une API, et un autre en charge de gérer le modèle des données traitées. 

En utilisant l’option {root:true} de la fonction dispatch, nous précisons à Vue.js que l’on veut effectuer l’opération dans le contexte du “namespace” global. C’est ainsi que vous pourrez éviter des problèmes de chemin, et simplement adresser un autre module par son nom et son action. C’est l’option à ne pas oublier si vous souhaitez faire communiquer facilement deux modules. 

import {ActionTree} from 'vuex';
import axios, {AxiosResponse} from 'axios';
import {Module} from 'vuex';
import {IExampleState} from '@/interfaces/IExampleState';
import {IRootState} from '@/interfaces/IRootState';

const actions: ActionTree<IExampleState, IRootState> = {
 loadResponse({dispatch}, question: string) {
 axios({
 method: 'GET',
 url: `theAllKnowingApi/${question}`,
 }).then((response: AxiosResponse) => {
 dispatch('exampleModelService/onResponse', response, {root: true});
 });
 },
};

const namespaced: boolean = true;

export const exampleComService: Module<IExampleState, IRootState> = {
 namespaced,
 actions,
};

example.com.service.ts 

import {ActionTree, GetterTree, Module, ModuleTree, MutationTree} from 'vuex';
import {IRootState} from '@/interfaces/IRootState';
import {IExampleState} from '@/interfaces/IExampleState';

const state: IExampleState = {
 example: '',
};

const actions: ActionTree<IExampleState, IRootState> = {
 onResponse({commit}, response: string) {
 commit('setExample', response);
 },
};

const mutations: MutationTree<IExampleState> = {
 setExample(currentState: IExampleState, payload: string) {
 currentState.example = payload;
 },
};

const getters: GetterTree<IExampleState, IRootState> = {
 getExample(currentState: IExampleState) {
 return currentState.example;
 },
};

const namespaced: boolean = true;

export const exampleModelService: Module<IExampleState, IRootState> = {
 namespaced,
 state,
 actions,
 mutations,
 getters,
};

example.model.service.ts 

Petite précision pour les interfaces : les types génériques Module et ActionTree prennent en paramètre le type du State courant et le type du State racine.  

Le state du module “ExempleModelService” étant composé d’une seule propriété “example” de type “String”, voici, en toute logique, son interface. Une logique identique pour le state du module racine ! 

export interface IExampleState {
 example: string;
}

Maintenant, nous allons montrer l’utilisation du plugin vuex-class, qui est tout simplement là pour nous aider avec les liens de nos modules, rendant le tout plus lisible et bien plus modulable. 

On importe les namespaces au début du composant :  

const exampleModelService = namespace('exampleModelService');
const exampleComService = namespace('exampleComService');

On fait la déclaration des attributs. Par exemple, pour un getter et une action et, au lieu de toujours devoir faire l’appel du module à chaque fois que l’on veut y accéder, il suffit d’utiliser ce que l’on a déclaré.  

En vue classique, déclaration et utilisation sont identiques : 

store.getters['path/to/module/example']
store.dispatch('loadResponse',{ question: 'Is math related to science ?' })

Avec Typescript :  

@exampleModelService.Getter('example')
private _getExample!: string;

@exampleComService.Action('loadResponse')
private _loadResponse!: (question: string) => {};

L’appel de l’action ou du getter : 

public created() {
 this._loadResponse('Is math related to science ?');
}
public get getExample(): string {
 return this._getExample;
}

Voilà ! Vous avez maintenant toutes les clefs en main pour correctement appréhender Vue avec Typescript. Vous gagnez à la fois en modularité et en flexibilité. Pas mal non ? Si l’idée vous intéresse, voici le github de vuex-class : https://github.com/ktsn/vuex-class. Vous y retrouverez l’équivalent décorateur pour toutes les manipulations possibles du store. Également le github de l’article : https://github.com/meritisgroup/vue-typescript-starter  pour avoir une version fonctionnel de tout ce qui a été expliqué. 

J’espère que cet article vous aura donné envie de passer à Typescript sur vos prochains projets Vue ! 

Pas encore de commentaires

Publier un commentaire

Auteur

Bastien Remond

Passionné de sciences et de nouvelles technologies, le web est mon domaine d'expertise.

Découvrir ses autres articles

Pas d'autres articles publiés par cet auteur pour le moment