Bases

Bienvenue dans la première page du manuel. Si c'est votre premier contact avec TypeScript, vous voudrez peut-être commencer avec les guides

Chaque valeur en JavaScript a un ensemble de comportements que l’on peut observer en exécutant diverses opérations. Cela paraît abstrait, mais considérons cet exemple d’opérations qu’on pourrait lancer sur une variable appelée message.

js
// Accès à la propriété "toLowerCase"
// de 'message' et appel de cette propriété
message.toLowerCase();
// Appel direct de 'message'
message();

Si on y va étape par étape, la première ligne accède à une propriété appelée toLowerCase puis l’appelle. La deuxième appelle message directement.

Mais en supposant qu’on ne connaît pas la valeur de message - et cela arrive souvent - nous ne pouvons pas dire quel résultat nous obtiendrons quand on essaie de lancer le code. Le résultat de chaque opération dépend entièrement de la valeur qu’on avait au départ.

  • Est-ce que message peut être appelé ?
  • Est-ce qu’il a une propriété toLowerCase ?
  • S’il en a une, est-ce que toLowerCase peut être appelée elle aussi ?
  • Si ces deux valeurs peuvent être appelées, qu’est-ce qu’elles retournent ?

Les réponses à toutes ces questions sont normalement des informations qu’on retient en écrivant du JavaScript, tout en espérant que notre mémoire ne nous trahira pas.

Supposons que message soit défini de cette façon.

js
const message = "Hello World!";

Comme vous pourrez peut-être le deviner, si nous essayons de lancer message.toLowerCase(), nous aurons le même string mais en minuscules.

Et cette seconde ligne ? Si vous êtes familier avec JavaScript, vous saurez qu’elle échouera avec l’exception :

txt
TypeError: message is not a function

Ce serait bien si on pouvait éviter ce genre d’erreurs.

Quand on lance notre code, la façon dont JavaScript décide comment agir est de trouver quel est le type de la valeur - quelles sortes de comportements et capacités possède-t-elle. C’est en partie ce que TypeError nous dit - le string "Hello World!" ne peut pas être appelé comme une fonction.

Pour certaines valeurs, comme les string et number, nous pouvons identifier leurs types à l’exécution grâce à l’opérateur typeof. Mais pour autre chose comme des fonctions, il n’y a aucun mécanisme pour faire de même. Considérons cette fonction par exemple :

js
function fn(x) {
return x.flip();
}

Nous pouvons observer en lisant le code que cette fonction ne fonctionnera que si elle reçoit un objet avec une propriété flip appelable, mais JavaScript ne remonte pas cette information pendant que l’on code. La seule façon de le savoir, c’est d’appeler la méthode et voir le résultat. Ce type de comportement rend la prédiction de ce que le code va faire difficile.

Vu de cette façon, un type permet de décrire quelles valeurs peuvent être passées à fn et quelles valeurs vont provoquer un bug. JavaScript ne fournit que du typage dynamique - vérifiable uniquement quand on lance le code.

L’alternative est d’utiliser un système de typage statique pour faire des prédictions sur le comportement du code à exécuter avant qu’il se lance.

Vérification statique de types

Nous avons eu un TypeError en essayant de nous servir d’un string en tant que fonction. La plupart des gens n’apprécient pas d’avoir des erreurs dans leur code - ce sont des bugs ! Et quand on écrit du nouveau code, nous faisons de notre mieux pour éviter les bugs.

Si nous ajoutons un bout de code, sauvegardons notre fichier, relançons notre code, et remarquons une erreur immédiatement, on pourrait isoler le problème assez vite ; mais ce n’est pas toujours le cas. Peut-être qu’on n’a pas assez testé notre code, donc il se peut qu’on ne tombe pas sur l’erreur assez tôt ! Ou alors, si on trouve l’erreur, on pourrait l’avoir trouvée après avoir fait une grosse refonte, ajouté beaucoup de code, et beaucoup creusé pour l’avoir trouvée.

Idéalement, on aurait un outil qui nous aiderait à trouver ces bugs avant que ce code se lance. Et c’est là que TypeScript intervient, avec son système de typage statique. Les systèmes de typage statique décrivent les comportements de nos valeurs une fois notre programme lancé. Un système de vérification de types comme TypeScript utilise ces informations pour nous dire quand le code risque de se comporter de façon imprévue.

ts
const message = "bonsoir";
 
message();
This expression is not callable. Type 'String' has no call signatures.2349This expression is not callable. Type 'String' has no call signatures.
Try

Exécuter cet exemple avec TypeScript va nous remonter une erreur avant même de lancer le code.

Problèmes qui ne crasheront pas le programme

Jusque-là, nous avons montré des cas où JavaScript indiquera qu’une erreur s’est produite. Ces cas apparaissent parce que la spécification ECMAScript possède des instructions précises sur la façon dont JavaScript doit se comporter s’il rencontre un cas inhabituel.

Par exemple, cette spécification dit qu’essayer d’appeler quelque chose qui ne peut pas être appelé provoque une erreur. Cela peut paraître comme un comportement évident, mais on aurait pu dire qu’accéder à une propriété qui n’existe pas dans un objet devrait aussi lancer une erreur. À la place, JavaScript nous retourne la valeur undefined :

js
const user = {
name: "Daniel",
age: 26,
};
user.location; // retourne undefined

Finalement, un système de vérification statique de types devra décider quel code doit être considéré comme une erreur, même s’il est du code JavaScript “valide” qui ne retournera pas d’erreur immédiate. Avec le code suivant, TypeScript lance une erreur à propos de location qui n’est pas défini:

ts
const user = {
name: "Daniel",
age: 26,
};
 
user.location;
Property 'location' does not exist on type '{ name: string; age: number; }'.2339Property 'location' does not exist on type '{ name: string; age: number; }'.
Try

Cela implique parfois de faire des concessions sur ce qu’il est possible de faire avec TypeScript, mais l’intention est de repérer les bugs légitimes dans votre programme. Et TypeScript repère beaucoup de bugs légitimes.

Par exemple, les fautes d’orthographes :

ts
const announcement = "Hello World!";
 
// Combien de temps mettrez-vous à repérer l'erreur ?
announcement.toLocaleLowercase();
announcement.toLocalLowerCase();
 
// On a probablement voulu écrire ça à la place...
announcement.toLocaleLowerCase();
Try

Les fonctions qui ne sont pas appelées :

ts
function flipCoin() {
// C'était censé être Math.random()
return Math.random < 0.5;
Operator '<' cannot be applied to types '() => number' and 'number'.2365Operator '<' cannot be applied to types '() => number' and 'number'.
}
Try

Ou de simples erreurs de logique.

ts
const value = Math.random() < 0.5 ? "a" : "b";
if (value !== "a") {
// ...
} else if (value === "b") {
This comparison appears to be unintentional because the types '"a"' and '"b"' have no overlap.2367This comparison appears to be unintentional because the types '"a"' and '"b"' have no overlap.
// Oups, impossible de l'atteindre
}
Try

Types dans l’outillage

TypeScript peut capturer les bugs quand on commet des erreurs dans notre code. C’est bien, mais il est également possible de ne pas en faire dès le départ.

TypeScript possède les informations nécessaires pour faire ses vérifications (on dit qu’il est un vérificateur de types, ou type-checker) : est-ce que la propriété ou variable à laquelle on tente d’accéder existe, quel est son type, quelles sont les opérations qu’on peut accomplir dessus. De ce fait, le langage peut suggérer les propriétés que vous tentez d’utiliser.

Cela signifie que TypeScript peut être également utilisé dans la modification de code, et le type-checker peut fournir messages d’erreur et autocomplétion pendant que vous écrivez. C’est en partie ce qu’il est signifié quand on parle de TypeScript dans l’outillage d’un projet.

ts
import express from "express";
const app = express();
 
app.get("/", function (req, res) {
res.sen
});
 
app.listen(3000);
Try

Un éditeur de code qui gère TypeScript peut fournir des “quick fixes” pour corriger automatiquement de petites erreurs, des suggestions de réorganisation, ainsi que des fonctionnalités de navigation pour trouver toutes les références à une variable, ainsi que sa définition initiale. Tout cela s’appuie sur le vérificateur de types et est probablement multi-plateformes, il est donc probable que votre IDE préféré supporte TypeScript.

tsc, le compilateur

On a beaucoup parlé du principe de vérification de types, mais pas du vérificateur de types. Ce vérificateur n’est autre que le compilateur TypeScript, tsc. Tout d’abord, il faut l’installer depuis npm.

sh
npm install -g typescript

Cette commande installe globalement le compilateur tsc. Si vous préférez utiliser une version locale de TypeScript, vous pouvez utiliser npx ou tout autre outil similaire.

Créons un dossier vide et un fichier hello.ts qui contient :

ts
// Dire bonjour.
console.log("bonjour !");
Try

Rien de fantastique : ce “hello world” est identique à un “hello world” en JavaScript. Maintenant, lançons la commande tsc qui a été installée avec le package typescript.

sh
tsc hello.ts

Et voilà… voilà quoi, exactement ? A priori, rien ne s’est passé. Cela dit, il n’y a eu aucune erreur, donc rien n’a été rapporté depuis la console.

En regardant de plus près, un nouveau fichier a été créé : dans le même dossier, il y a un hello.js à côté de hello.ts. C’est le résultat de la compilation de hello.ts en un fichier JavaScript standard. Observons le contenu du fichier émis par TypeScript :

js
// Greets the world.
console.log("Hello world!");

Ici, TypeScript n’a pas eu grand-chose à transformer, donc le code final est identique au code de départ. Le compilateur essaie toujours d’émettre du code qui ressemble à ce qu’écrirait une vraie personne. Ce n’est pas toujours facile, mais TypeScript conserve l’indentation, fait attention quand le code s’étend sur beaucoup de lignes, et essaie de conserver les commentaires.

Essayons d’introduire une erreur de vérification en modifiant hello.ts :

ts
// Fonction de salutation générique et de haut niveau :
function greet(person, date) {
console.log(`Bonjour ${person}, nous sommes le ${date} !`);
}
 
greet("Brendan");
Try

Si on lance tsc hello.ts à nouveau, on remarque qu’on a bel et bien une erreur.

txt
Expected 2 arguments, but got 1.

TypeScript nous informe qu’on a oublié de passer un argument à la fonction greet, à raison. Jusque-là nous avons écrit ce qui peut être vu comme du JavaScript valide, et la vérification de types a quand même pu repérer des erreurs. Merci TypeScript !

Émissions de fichier avec erreurs

Vous n’aurez peut-être pas remarqué que le fichier hello.js a encore changé. Si vous l’ouvrez à nouveau, vous verrez que son contenu est le même que le fichier source. Cela peut surprendre, étant donné que tsc a rapporté une erreur, mais ce comportement est concordant avec les valeurs fondamentales de TypeScript : la plupart du temps, vous saurez mieux.

La vérification de types limite les sortes de programmes que vous pouvez lancer. Il y a donc un compromis à atteindre sur ce que le vérificateur considère acceptable. D’habitude, cela ne pose aucun problème, mais il y a des situations où cette rigueur est contre-productive. Par exemple, imaginez que vous migrez du code JavaScript en TypeScript, introduisant de ce fait des erreurs de typage. À la fin, vous corrigerez ces erreurs, mais ce code JavaScript fonctionnait déjà. Le convertir en TypeScript ne devrait rien y changer.

Bien sûr, avec le temps, vous voudrez peut-être qu’il soit plus restrictif par rapport aux erreurs, et faire en sorte que TypeScript agisse un peu plus strictement. Dans ce cas, vous pouvez utiliser l’option de compilateur noEmitOnError. Avec cette option, modifiez hello.ts et lancez tsc :

sh
tsc --noEmitOnError hello.ts

hello.js ne va pas se mettre à jour.

Types explicites

Jusque-là, nous n’avons pas précisé ce que sont person ou date. Modifions notre code, et informons TypeScript que person est un string, et que date doit être un objet Date. On utilisera aussi la méthode toDateString() de date.

ts
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
Try

Ce qu’on vient d’ajouter s’appelle des annotations de types sur person et date, pour décrire les valeurs dont greet peut se servir. Cet exemple peut se lire ”greet prend une person de type string, et une date de type Date“.

Armé de cette information, TypeScript peut nous prévenir quand greet pourrait être utilisé de façon incorrecte. Par exemple…

ts
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
 
greet("Maddison", Date());
Argument of type 'string' is not assignable to parameter of type 'Date'.2345Argument of type 'string' is not assignable to parameter of type 'Date'.
Try

Quoi ? TypeScript a rapporté une erreur sur le deuxième argument, mais pourquoi ?

Appeler Date() en JavaScript retourne un string. Mais construire une Date avec new Date() nous donne ce qu’on attend.

L’erreur peut être rapidement réparée :

ts
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
 
greet("Maddison", new Date());
Try

Cela dit, nous ne sommes pas obligés de définir des annotations de types tout le temps. Dans plusieurs situations, TypeScript peut simplement inférer (ou “déduire”) les types pour nous même si on les omet.

ts
let msg = "bien le bonjour !";
let msg: string
Try

Même si on n’a pas dit que msg avait le type string, TypeScript a su le déduire tout seul. C’est une fonctionnalité, et il vaut mieux laisser TypeScript faire le travail d’inférence s’il déduit correctement le type.

Note : le message qui s’affiche dans la bulle serait ce que votre éditeur afficherait si vous survolez la variable.

Effacement de Types

Compilons la méthode greet avec tsc et observons le résultat :

ts
"use strict";
function greet(person, date) {
console.log("Hello ".concat(person, ", today is ").concat(date.toDateString(), "!"));
}
greet("Maddison", new Date());
 
Try

Remarquons deux points sur le résultat :

  1. Les paramètres person et date n’ont pas d’annotations de types.
  2. Notre “template string” - la chaîne de caractères qui utilise les apostrophes inverses (`) - a été convertie en utilisant une concaténation classique (+).

Nous parlerons du deuxième point plus tard, mais concentrons-nous sur le premier. Les annotations de types ne font pas partie de la spécification JavaScript (ou ECMAScript si on veut chercher la petite bête), donc TypeScript ne peut pas s’exécuter dans un navigateur sans modification préalable. C’est tout l’intérêt d’un compilateur TypeScript - il permet de transformer le code et lui permettre de se lancer. La plupart du code propre à TypeScript est effacée, y compris nos annotations de types.

Souvenez-vous : Les annotations de type ne doivent jamais changer l’exécution de votre code.

Nivellement par le bas

Une autre différence entre le code compilé et code source, la transformation de notre chaîne de caractères :

js
`Hello ${person}, today is ${date.toDateString()}!`;

vers

js
"Hello " + person + ", today is " + date.toDateString() + "!";

Pourquoi cela ?

Les Template strings sont une fonctionnalité d’ECMAScript appelée ECMAScript 2015 (mais aussi ECMAScript 6, ES2015, ES6, etc. - c’est compliqué). TypeScript peut réécrire le code de versions récentes d’ECMAScript vers certaines plus anciennes, tel que ECMAScript 3 ou ECMAScript 5 (ES3 et ES5). Le fait de passer d’une version plus récente ou plus neuve d’ECMAScript vers une autre plus basse s’appelle le nivellement vers le bas.

Par défaut, TypeScript vise ES3, une version extrêmement vieille d’ECMAScript. Nous aurions pu choisir une version un peu plus récente avec l’option target. Compiler avec --target es2015 compile TypeScript en visant ECMAScript 2015, donc tout environnement supportant ES2015 peut lancer ce code. Lancer tsc --target es2015 hello.ts nous mène au résultat suivant :

js
function greet(person, date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
greet("Maddison", new Date());

La cible par défaut est ES3, mais tous les navigateurs modernes supportent ES2015.

Donc la plupart des développeurs peuvent viser ES2015 ou plus haut, sauf si supporter d’anciens navigateurs est une contrainte.

Degré de rigueur

TypeScript est utilisé pour diverses raisons. Certains développeurs veulent une expérience laxiste et volontaire. TypeScript peut valider certaines parties du programme uniquement, tout en laissant ses capacités disponibles. C’est l’expérience par défaut avec TypeScript, où le typage est optionnel, l’inférence déduit des types vagues, et aucune valeur null/undefined n’est vérifiée. Tout comme tsc émet des fichiers même avec des erreurs, ces comportements par défaut sont en place pour qu’ils ne vous entravent pas. Ce serait une première étape désirable si vous migrez du code JavaScript.

D’autres utilisateurs souhaitent que TypeScript valide et soit strict le plus possible, d’où plusieurs options disponibles à cet effet. Ces paramètres permettent d’avoir des “niveaux” de rigueur (allant du laxiste au plus strict possible) plutôt que d’avoir deux options binaires (faire de la vérification de code ou pas du tout). Plus vous montez en niveaux de rigueur, plus TypeScript vous assistera avec la validation et la vérification. Cela peut nécessiter du travail supplémentaire, mais il se rentabilise sur le long terme. Si possible, une nouvelle base de code doit toujours avoir les vérifications strictes activées.

TypeScript a plusieurs options de rigueur qui peuvent être activées ou pas, et tous nos exemples les suivront, sauf si le contraire est mentionné. L’option strict dans la ligne de commande, ou "strict": true dans le fichier tsconfig.json les active toutes ensemble, mais il est possible de les désactiver individuellement. Les deux options les plus importantes sont noImplicitAny et strictNullChecks.

noImplicitAny

À certains endroits, TypeScript n’essaie pas d’inférer de types et va rendre le type le plus laxiste : any. Ce n’est pas forcément un problème - de toute façon, le type any est ce que JavaScript va vous donner.

Cependant, utiliser any ne donne plus aucun intérêt d’utiliser TypeScript. Plus votre programme est couvert par du typage, plus vous serez épaulé en termes de validation et d’outillage, et moins vous aurez de bugs. Activer l’option noImplicitAny va remonter une erreur pour toute variable avec un type qui a été inféré en any.

strictNullChecks

Par défaut, les valeurs comme null et undefined sont assignables à tout autre type. Cela peut s’avérer pratique, mais oublier de gérer null et undefined est la cause d’innombrables bugs - certains le considèrent comme une erreur coûtant des milliards de dollars ! L’option strictNullChecks rend la gestion de null et undefined plus explicite, et nous épargne les maux de tête autour du fait de devoir gérer null et undefined.

The TypeScript docs are an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
RCRyan Cavanaugh  (55)
  (6)
DRDaniel Rosenwasser  (4)
EBEli Barzilay  (2)
3+

Last updated: 16 déc. 2024