Progressivement TypeScript devient la norme dans les projets Web. D’ailleurs, ces derniers temps plusieurs projets open source ont fait le choix de l’adopter pour leur base de code comme Slack et Lyft. Loin d’être les seuls, les développeurs de Jest et Yarn, de chez Facebook, ont également entrepris de remplacer Flow par TypeScript. Cela démontre un tournant significatif, en effet Flow est un concurrent direct de TypeScript qui a pourtant été conçu par Facebook.
Même décision prise par Evan You et son équipe pour la base de code de la version 3.0 de Vue.js. Il est possible de trouver bon nombre d’annonces similaires sur Twitter.
On pourrait aussi évoquer Angular qui depuis sa réécriture en 2016 utilise par défaut TypeScript.
Bref, c’est un langage qui a le vent en poupe et nous allons voir dans cet article comment l’utiliser dans une application Vue.js.
TypeScript kézako ?
Ce langage est de plus en plus utilisé, mais pourquoi ? Quels sont ses avantages ?
TypeScript est un langage de programmation développé par Microsoft et sorti en 2012. Il s’agit d’un sur-ensemble de JavaScript.
Il ajoute un système de typage statique puissant, grâce auquel il est possible de :
- typer n’importe quelle variable, propriété d’une classe ou paramètre d’une fonction ;
- utiliser des interfaces ;
- utiliser les génériques sur des classes ;
- créer des types union ;
- créer des enums ;
- l’assignation de valeur
null
etundefined
doivent être explicité dans le type de l’élément.
Ceci permet d’avoir un code qui est plus expressif, plus sûr car les erreurs de type sont vérifiées à la compilation. En effet, les sources écrites en TypeScript sont transpilées en JavaScript pour être exécutées à l’aide de la commande tsc
. De ce fait, les programmes TypeScript sont 100% compatibles avec les différents moteurs JavaScript existants. Au-delà de la détection des erreurs au plus tôt, les IDE modernes comme WebStorm ou VSCode peuvent s’appuyer sur la définition des types de notre application pour proposer une auto-complétion efficace.
A noter que les fichiers TypeScript possèdent l’extension .ts
pour les différencier des fichiers JavaScript.
Pour aller plus loin n’hésitez pas à consulter la documentation officielle qui est très bien faite, à commencer par le guide présentant TypeScript aux développeurs JavaScript.
Utilisation de bibliothèques
Beaucoup de bibliothèques utilisées encore aujourd’hui sont écrites en JavaScript et même s’il est possible de les utiliser sans problèmes dans un projet TypeScript, il serait préférable d’avoir la définition de leurs APIs publiques. Tout simplement pour profiter des mêmes avantages que pour notre code applicatif et ainsi éviter les erreurs d’utilisation de ces bibliothèques.
Heureusement pour nous, un grand nombre de définitions est disponible sur NPM sous forme de dépendances dont le nom commence généralement par @types
, par exemple pour Jest avec le package @types/jest. La plupart de ces définitions proviennent du projet qui en possède aujourd’hui pratiquement 6000.
TypeScript et Vue.js
Tout comme React avec les fichiers .tsx
, Vue.js possède un support de TypeScript complet incluant les principaux types de la bibliothèque mais aussi les types des plugins officiels vue-router et vuex.
En plus de la syntaxe classique en plain object, ce support permet aussi de définir les composants sous forme de classe.
Syntaxe plain object
Elle repose sur la syntaxe de base avec les types en plus, pour l’activer il suffit de passer le composant à la fonction Vue.extend
.
Voici un exemple :
export default Vue.extend({ props: { brand: String, year: Number }, created (): void { // Le composant a été instancié : il est possible par exemple de faire un appel d'API pour récupérer les données du véhicule }, watch: { year (): void { console.log('Year changed ' + newValue); } }, computed: { description (): string { return `${this.brand} since ${this.year}`; } }, })
Syntaxe sous forme de classe
Vue.js propose aussi d’écrire ses composants sous forme de classe en utilisant des décorateurs, assez proche de ce qui se fait en Angular. Concrètement, la classe représentant le composant doit être décorée par @Component
. Il existe un décorateur pour chaque fonctionnalité d’un composant.
- Les propriétés d’entrée ou props correspondent à des propriétés de la classe décorées par
@Prop
. - Pour observer les changements d’une propriété – par exemple
my-watched-property
– il faut décorer une méthode avec@Watch('my-watched-property')
. - Pour définir une propriété interne du composant – que l’on déclare habituellement avec la fonction data – il suffit de déclarer une simple propriété de classe sans décorateur.
- Les computed properties deviennent de simple getter en utilisant l’opérateur get.
- Les hooks du cycle de vie se traduisent par des méthodes nommées
created
,mounted
ou encoredestroyed
.
Pour illustrer reprenons l’exemple précédent :
import { Component, Prop, Vue } from 'vue-property-decorator'; @Component export default class Car extends Vue { @Prop() private brand?: string; @Prop() private year?: number; created(): void { // Le composant a été instancié : il est possible par exemple de faire un appel d'API pour récupérer les données du véhicule } @Watch('year') onYearChanged(newValue: number) { console.log('Year changed ' + newValue); } get description() { return `${this.brand} since ${this.year}`; } }
Maintenant, comment faire pour déclarer la dépendance vers un autre composant ? Il faut savoir que le décorateur @Component
accepte un paramètre qui est un objet similaire à un composant Vue.js.
Ainsi il est possible d’utiliser la syntaxe old-fashioned way pour définir ce dont nous avons besoin, comme la dépendance vers d’autres composants et c’est – il me semble – la seule façon de faire.
import Basket from "./Basket.vue"; import { Component, Prop, Vue, Watch } from 'vue-property-decorator'; @Component({ components: { Basket } }) export default class Car extends Vue { }
A noter que ce décorateur est disponible dans le paquet officiel alors que les autres décorateurs font parties du paquet non officiel
Cette syntaxe rend, de mon point de vue, l’écriture du composant plus claire car les différentes sections du composant sont bien séparées. Ci-dessous un tableau comparant les deux syntaxes : Tout d’abord il faut installer Vue CLI, si ce n’est pas déjà fait. L’initialisation d’un nouveau projet se fait avec la commande Comme illustré ci-dessus, pour configurer TypeScript il faut choisir l’option “Manually select features” ce qui permet d’activer les fonctionnalités suivantes : La solution ci-dessous fonctionne pour les projets ayant été initialisés par vue-cli 3.0 et utilisant donc Vue CLI service. Cependant je vous conseille tout de même d’utiliser la dernière version de Vue CLI. Puis il suffit de lancer la commande suivante à la racine de votre projet : Et c’est tout. En effet, cette commande pose les mêmes questions que lors de la création d’un projet from scratch et va : Concrètement, les devDependencies suivantes ont été ajoutées : Pour utiliser la syntaxe orientée classe pour les composants, il faut aussi ajouter les dépendances vue-class-component et vue-property-decorator dans la section dependencies cette fois-ci : Des fichiers de configuration notamment pour TypeScript sont ajoutés au projet : Il est aussi nécessaire d’indiquer à ESLint de prendre en compte les règles TypeScript en ajoutant à sa configuration L’extension des fichiers Les composants qui ont été écrits avec la syntaxe classique, et ceux écrits en TypeScript peuvent cohabiter sans problème. Dans les fichiers “vue”, pour indiquer qu’un composant est écrit en TypeScript, il est nécessaire d’ajouter l’attribut lang avec la valeur A noter qu’il est impératif de préciser l’extension Vous trouverez sur ce dépôt un exemple de projet basique qui a été migré en TypeScript. Ce commit montre quelles modifications ont été nécessaires pour réaliser cette migration. Pour les projets utilisant une version plus ancienne de Vue.js (< 2.5), la solution ci-dessus ne fonctionnera pas. En effet, il est nécessaire d’avoir un projet utilisant Vue CLI service pour mettre en place cette architecture. Pas de panique, cependant, ce n’est pas perdu mais cela implique de migrer votre projet vers une version récente de Vue.js. Heureusement, l’un des points fort de ce framework est d’être rétro-compatible, vous ne devriez donc pas rencontrer trop d’écueils. Vous trouverez un exemple de migration sur cet autre dépôt en regardant ce commit. Dans un composant en TypeScript ( Lors du bootstrap de l’application Vue.js dans le fichier main.ts : utiliser la propriété Comme nous l’avons vu, le coût du passage à TypeScript est très faible, alors qu’il apporte énormément d’avantages en contrepartie : Tout ceci explique pourquoi tant de projets se tournent vers ce langage et délaissent petit à petit le bon vieux JavaScript. Ainsi, aucune raison de ne pas sauter le pas et de migrer son application écrite en Vue.js, d’autant qu’il est possible de le faire de manière incrémentale. De plus, la version 3.0 de Vue.js, dont la Bêta est sortie fin Avril, apporte un meilleur support de TypeScript. En effet, le code source de Vue a été entièrement réécrit en TypeScript. Il sera alors possible d’utiliser les vrais types utilisés par Vue.js en interne, plutôt qu’une définition écrite à côté. Spoiler alert, cette version apporte de meilleures performances, jusqu’à deux fois plus rapide, un runtime plus léger et reste rétro-compatible. Mais surtout elle apporte de nouvelles fonctionnalités comme l’API Composition qui s’inspire des Hooks de React.Comparatif
Plain object component
Class based component
export default {
@Component
export default class HelloWorld extends Vue {
Propriété interne (data)
data() {
return {
firstName: 'Alice',
lastName: 'Bob',
}
},
firstName: string = 'Alice';
lastName: string = 'Bob';
Les propriétés externes (prop)
props: ['msg', 'age'],
@Prop() msg?: string;
@Prop() age?: number;
Les computed properties
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`;
},
},
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
Les hooks du cycle de vie
created() {
},
mounted() {
},
created() {
}
mounted() {
}
Les méthodes
methods: {
compute1(param1, param2) {
// ...
},
compute2(param1, param2) {
// ...
},
},
compute1(param1: string, param1: number) {
// ...
}
compute2(param1: string, param1: number) {
// ...
}
Les watchers
watch: {
firstName(value, oldValue) {
console.log('firstName changed to', value);
}
}
}
@Watch('firstName', {deep: true})
firstNameChanged(value: string, oldValue: string) {
console.log('firstName changed to', value);
}
}
Comment l’utiliser dans mon projet ?
From scratch
npm install -g @vue/cli
create
qui offre la possibilité de choisir un certain nombre d’options de configuration.vue create my-app
Sur un projet existant initié avec Vue CLI
npm install -g @vue/cli
vue add @vue/typescript
Behind the magic
npm -D i @types/vue @vue/cli-plugin-typescript @vue/eslint-config-typescript typescript
npm -S i vue-class-component vue-property-decorator
Configuration
@vue/typescript
dans la section extends
, par défaut elle se trouve dans le fichier .eslintrc.js
.module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/essential',
'eslint:recommended',
'@vue/typescript' // à ajouter
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
},
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
Conversion du code
.js
doit être changée en .ts
pour que cela fonctionne.ts
au bloc script :<script lang="ts">
import { Prop } from 'vue-property-decorator';
import Vue from 'vue';
import Component from 'vue-class-component';
@Component
export default class HelloWorld extends Vue {
@Prop()
msg?: string;
secretVisible = false;
created() {
this.secretVisible = true;
}
}
</script>
.vue
lorsque de l’import d’un composant dans un fichier .ts
ou dans un autre composant écrit en TypeScript (lang="ts"
), sinon le composant n’est pas résolu. Pour plus d’informations je vous invite à regarder cet issue.Mon projet utilise une ancienne version de Vue.js
Erreurs classiques que vous pourriez rencontrer
lang="ts"
), ne pas oublier d’ajouter l’extension .vue
lors de l’import d’un composant contenu dans un fichier vue comme indiqué dans le paragraphe Conversion du code.render
à la place de template
car le compilateur de template n’est plus embarqué au runtime pour les applications utilisant Vue CLI service pour des raisons de performances. Ce qui vous permettra d’éviter l’erreur [Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available
.new Vue({
render: h => h(App),
}).$mount('#app');
Conclusion