Qu’est-ce que c’est ?


TypeScript est une surcouche du langage JavaScript de plus en plus utilisée, dont la version 2.0 a été publiée très récemment.
Nous allons voir dans cet article les possibilités offertes par ce nouveau langage, dans sa version 2.0.

Le typage


La base de TypeScript est d’apporter le typage au JavaScript. Cela permet d’avoir un code plus lisible, et une meilleure organisation. TypeScript supporte l’inférence de type, mais veillez à ne pas en abuser pour garder le code lisible.

Les types de base

TypeScript possède trois types de base :

  • boolean
  • string
  • number

On peut noter qu’il n’y a pas de différenciation entre int , float , double etc.

La syntaxe est la suivante :

pour les variables

pour les fonctions

Viennent ensuite les types spéciaux :

  • null
  • undefined
  • any
  • never

Les habitués du JavaScript noteront ici une différence majeure apportée par TypeScript. En effet, null et undefined sont maintenant des types à part entière. De plus, avec le flag --strictNullChecks , tous les types sont non-nullable par défaut. Ainsi, si une variable peut être null ou undefined, il faut faire une union de type, comme ceci:

On peut aussi utiliser les optionnels, dont nous parlerons plus tard.

Le type any est le “joker” qui permet à une variable de recevoir n’importe quel type. Ce type permet surtout de garder la compatibilité avec le JavaScript, mais va à l’encontre de la philosophie TypeScript qui apporte les types. Il est donc à utiliser avec parcimonie.

Le type never traduit les cas impossibles. Il est surtout utilisé comme type de retour des fonctions qui ne terminent jamais, comme les exemples suivants :

Les fonctions ne se terminant jamais ont un type de retour établi par défaut à never.

never est un sous-type de tous les types existants. Il est donc attribuable à tous les types, et est ignoré dans les unions.
Une fonction ayant un type de retour à never peut donc être appelée lorsqu’on attend un type de retour plus restrictif.

Les classes et les Interfaces

TypeScript apporte au JavaScript une programmation orientée objet classique. On peut donc créer des classes et des interfaces, de la manière suivante :

Le mot-clé class permet de créer une classe, qui peut contenir des propriétés et des méthodes.
Le mot-clé interface permet de définir une interface, qui peut contenir une liste de méthodes. Les classes utilisant le mot-clé implements sur cette interface devront fournir une implémentation de ces méthodes.

Les mots-clés public, private, et protected définissent le niveau d’accessibilité des propriétés et des méthodes de la classe.

  • une propriété public est accessible de n’importe où.
  • une propriété private n’est accessible qu’à l’intérieur de la classe la contenant.
  • une propriété protected est accessible à partir de la classe la contenant, et de toutes les classes en héritant (cf ci-dessous).

Les mots-clés get et set permettent de définir des accesseurs. Ici, les accesseurs identifier pour permettre un accès à la propriété uniqueIdentifier qui n’est pas visible en dehors de la classe. Ainsi, grâce à l’accesseur get identifier (), on pourra connaître le nom de l’utilisateur sans pouvoir le changer. L’accesseur set identifier (newName: string) permet de changer la valeur de la propriété uniqueIdentifier . Vous pourriez vous demander à quoi bon mettre une propriété en private si c’est pour offrir la possibilité de la changer après coup. Et bien, il y a plusieurs raisons:

  • Vous n’êtes en aucun cas obligé de fournir les deux méthodes. Il se peut que nous ayons besoin uniquement d’un getter et non d’un setter, ou inversement.
  • Vous pouvez vouloir faire du traitement, comme par exemple:

Les accesseurs s’utilisent de manière transparente, comme s’il s’agissait d’une propriété publique :

Une classe peut hériter d’une autre classe, en utilisant le mot-clé extends. Une classe fille possédera les attributs et les méthodes de la classe mère.

Ici, la classe Car hérite de la classe Vehicle. Elle a donc accès à la propriété name, et à la méthode move. Les classes ayant un constructeur et héritant d’une autre classe doivent appeler le constructeur de leur classe mère avec le mot-clé super. Ce mot-clé permet d’accéder à la classe mère de façon générale.
Une classe fille peut surcharger les méthodes de la classe mère, pour fournir sa propre implémentation.

Une classe peut contenir des propriétés ou méthodes abstraites. Celle-ci sont définies par le mot-clé abstract. Ces propriétés ou méthodes voient leur signature définies dans la classe, mais pas leur implémentation. Une classe contenant une ou plusieurs propriétés ou méthodes abstraites doit être elle-même abstraite.
Une classe héritant d’une classe abstraite doit implémenter toutes les propriétés et méthodes abstraites, ou les redéfinir en tant qu’abstraites, devenant alors elle-même abstraite.

 

Les types génériques

Typescript supporte les types génériques. Une fonction utilisant un type générique est écrite de la manière suivante :

La fonction ci-dessus accepte n’importe quel type pour le paramètre arg. Nous avons pourtant vu précédement que le type any servait déjà de “joker” permettant d’accueillir n’importe quel type. Ainsi, la fonction ci-dessus aurait pu être écrite de la manière suivante :

Là aussi, le paramètre arg peut recevoir n’importe quel type. Mais le type générique apporte plus. En effet, le compilateur possède l’information que la fonction prend un paramètre de type T, et renvoie une donnée du même type.
Si on donne un type string en entrée, la fonction retournera un type string, contrairement à la version utilisant le type any, qui pourrait recevoir un type string, effectuer des opérations et retourner un type number.

Pour appeler une fonction possédant un type générique, on s’y prend de la manière suivante :

Comme vous pouvez le voir, on précise explicitement le type utilisé entre chevrons. On peut aussi laisser le compilateur trouver tout seul quel type est utilisé à partir du type des arguments.

Cette méthode permet d’alléger le code et d’éviter de retaper des informations inutiles, ce qui permet une meilleure lisibilité. Cependant, il se peut que parfois le compilateur n’arrive pas à décoder tout seul le type utilisé, et que la première méthode soit nécessaire.

Nous ne détaillerons pas plus les types génériques ici, mais vous pouvez approfondir le sujet en allant sur le site de TypeScript.

La compilation


Pour compiler votre fichier .ts, téléchargez le compilateur en tapant la commande suivante :

Ensuite, tapez la commande suivante :

Vous pouvez ensuite rajouter des options de compilation, comme ceci :

Vous aurez en sortie un fichier nommé file.js

NOTE: Pour effectuer de simples tests, vous pouvez utiliser le playground fourni pas TypeScript.

En conséquence directe du typage, TypeScript offre une vérification à la compilation. On a donc les vérifications basiques, telles que des appels à des fonctions avec des paramètres qui ne sont pas du type souhaité, mais aussi des éléments plus poussés.

Code inaccessible

TypeScript est capable de détecter les parties du code qui ne seront jamais accessibles.

Retour de fonctions implicites

TypeScript détecte les chemins n’ayant aucun retour dans les fonctions. Par défaut, un undefined est retourné. Le flag --noImplicitReturn permet d’avoir une erreur à la compilation.

Variables utilisées avant d’être assignées

Avec le flag --strictNullChecks , TypeScript envoie une erreur lorsqu’une variable est utilisée avant d’avoir été initialisée. L’assignation de la variable doit être faite dans tous les chemins possibles de la fonction.

Détection des types

TypeScript est capable de spécifier le type d’une variable à un instant précis du code, en fonction des différents chemins possibles.

Cette fonctionnalité est particulèrement intéressante avec le flag --strictNullChecks , qui explicite les types null et undefined.

D’autres outils appréciables


Nous avons déjà vu beaucoup de fonctionnalités apportant de la clarté au code, et une sécurité en comparaison d’un code JavaScript. Mais certaines autres méritent d’être mentionnées.

L’import et l’export

Nous n’avons pas envie de recoder toutes les fonctionnalités de base à chaque projet. Pour éviter cela, TypeScript met en place le système de modules. Pour importer un module, il suffit d’utiliser le mot-clé import et de spécifier la source.

Notez qu’on ne spécifie pas l’extension du fichier dans le chemin suivant from.

Il est possible de renommer ce que l’on importe

On peut aussi importer tout un module sous un seul et même nom, qui sera ensuite utilisé pour accéder à ce que contient le module.

Par défaut, les déclarations (classe, variables, fonctions etc.) ne sont pas visibles en dehors du module. Il faut ajouter le mot-clé export pour les rendre visibles.

Il existe des fichiers de déclarations (extention .d.ts) qui permettent de définir un type pour des bibliothèques existantes en JavaScript. Sans ces fichiers, il ne serait pas possible d’utiliser l’immense réserve de bibliothèques de JavaScript, ce qui serait très regrettable.

Pour installer un fichier de déclaration, tapez la commande suivante:

Généralement, le fichier de déclaration possède le même nom que la bibliothèque. Mais si ce n’est pas le cas, vous pouvez chercher le paquet correspondant à votre bibliothèque.

Une fois installé, vous pouvez l’importer

Variables en lecture seulement

Il est possible de déclarer une variable comme constante. Une variable constante doit être initialisée à sa déclaration et ne pourra jamais être modifiée ensuite.

TypeScript permet aussi de précéder la déclaration d’une propriété par le mot-clé readonly. Ces propriétés seront disponibles à la lecture, mais pas à l’écriture. Seule exception à cette règle : l’assignation d’une valeur à une variable en lecture seulement dans un constructeur.

Attention: dans certains cas, TypeScript considère des éléments comme readonly, même s’ils ne le sont pas déclarés explicitement. Ainsi, une propriété déclarée avec une accesseur get mais pas d’accesseur set est implicitement readonly, de même que les membres d’une énumération.

Paramètres optionnels

TypeScript supporte les paramètres optionnels. Ceux-ci sont traduits en rajoutant le type undefined au type de base, s’il n’ est pas déjà présent.
Ainsi, les deux types ci-dessous sont strictement équivalents.

Opérateur !

Situé après une expression, cet opérateur indique au compilateur que le résultat de l’expression est non-null. Parce que parfois, vous savez qu’un élément est non-null, mais le compilateur ne le sait pas.

Async / Await

TypeScript permet, avec l’option de compilation --targetES6, d’utiliser les mots-clés async et await.

Dans le code ci-dessus, la fonction needTime renvoie une promesse. Le retour est donc asynchrone. La fonction ping doit attendre le résultat de la fonction needTime, elle utilise donc le mot-clé await. Comme elle attend, elle doit être marquée comme asynchrone à l’aide du mot async. La fonction main attend le résultat da la fonction ping avec await, et doit donc elle aussi être marquée comme async.

Si vous testez ce code, les messages ‘ping n°X’ s’afficheront toutes les 420 millisecondes, puis ensuite le message ‘main over’.
En revanche, si vous supprimez les mots-clés async et await de la fonction main, le message ‘main over’ apparaîtra avant les autres messages, parce que la fonction main n’aura pas attendu la fonction ping.

NOTE: L’option de compilation --targetES6 génère du code qui n’est pas encore supporté par tous les navigateurs. Vérifiez la compatibilité avec vos navigateurs cibles avant de l’utiliser.

Conclusion

TypeScript est donc une surcouche du JavaScript, mais bien plus encore. C’est un langage orienté objet à part entière. On peut, par exemple, implémenter des design patterns propres à ce type de langage, comme le visiteur (cf annexes).

De gros projets se développent avec TypeScript, comme le framework Angular 2 développé par Google, ou encore Dojo 2.

Annexes


Voici une implémentation de design pattern visiteur en langage TypeScript :