Types et instructions basiques
Variable et portée
Pour déclarer une variable en TypeScript, il existe trois mots-clés, chacun ayant une utilité bien distincte : var, let et const. Ces derniers vont influer sur la portée de la variable qu’ils déclarent.
La portée d’une variable (aussi appelée scope ou durée de vie de la variable) correspond à la portion de code dans laquelle la variable existe et est accessible.
Le mot-clé var est voué à ne plus être utilisé, au profit du couple let/const. Par conséquent, dans du code TypeScript moderne, il est peu probable de rencontrer ce mot-clé. Néanmoins, il est tout de même intéressant de comprendre son fonctionnement.
Une variable déclarée avec le mot-clé var a une portée équivalente au bloc fonction dans lequel elle a été définie.
Exemple :
function fn() {
var firstName = "Evelyn";
// Log: Evelyn
console.log(firstName);
}
// Compilation Error TS2304:
// Cannot find name 'firstName'.
console.log(firstName);
À la dernière ligne de cet exemple, la variable firstName est utilisée à l’extérieur de la fonction dans laquelle elle a été déclarée. Le compilateur de TypeScript lève...
Types basiques
1. Introduction
Comme expliqué dans le chapitre Introduction, JavaScript est un langage à typage dynamique. Les types sont donc déterminés lors de l’interprétation du code. Toutefois, il est possible de retrouver ces types à l’aide de l’opérateur typeof. TypeScript propose d’ajouter du typage statique, permettant ainsi de déterminer les types lors de la phase de compilation.
Dans la suite de cet ouvrage, l’appellation type JavaScript fera référence au type déterminé par l’interpréteur. Dans les autres cas, il s’agit d’un type TypeScript.
2. Types primitifs basiques
TypeScript dispose des types primitifs suivants : string, number, boolean, null, undefined, symbol et bigint. Pour typer une variable, il faut préciser son type juste après l’avoir déclarée.
Syntaxe :
let/const variable: type;
Exemple :
const firstName: string = "Evelyn";
const age: number = 34;
const isCEO: boolean = false;
Une fois la variable typée, l’environnement de développement fournit l’autocomplétion sur celle-ci et le compilateur de TypeScript peut remonter des erreurs liées au typage.
Exemple (Visual Studio Code) :
Voici quelques cas d’erreurs classiques remontés par TypeScript au sein de Visual Studio Code :
-
L’utilisation d’une méthode ou d’une propriété n’appartenant pas à un type.
Le compilateur de TypeScript remonte une erreur sur la dernière ligne en indiquant que la méthode toExponential() n’existe pas sur le type string. Si ce code était exécuté au sein d’un navigateur, il provoquerait une erreur indiquant que firstName.toExponential() n’est pas une fonction.
-
L’affectation d’une valeur ayant un type différent de celui déclaré au niveau d’une variable.
Ce code ne produit pas d’erreur s’il est exécuté au sein d’un navigateur. Le compilateur de TypeScript empêche d’affecter une valeur d’un type différent de celui de la déclaration de la variable, afin de garder une consistance dans l’écriture du code.
Il est important de ne pas confondre les types primitifs string, boolean et number avec...
Décomposition
La décomposition est apparue avec la sortie d’ECMAScript 2015 (cf. chapitre Introduction). Elle peut s’utiliser avec les tableaux ou les objets, et permet d’extraire tout ou partie de ces éléments pour les mettre dans des variables distinctes.
Syntaxe (objet) :
let/const {variable, variable2} = { variable: 1, variable2: 2};
Exemple (objet) :
const person = {
firstName: "Evelyn",
lastName: "Miller",
age: 34
};
const { firstName, lastName, age } = person;
// Log: Evelyn
console.log(firstName);
// Log: Miller
console.log(lastName);
// Log: 34
console.log(age);
Syntaxe (tableau) :
let/const [variable, variable2] = [1, 2];
Exemple (tableau) :
const [name1, name2] = ["Evelyn", "John", "Bryan"];
// Log: Evelyn
console.log(name1);
// Log: John
console.log(name2);
La décomposition des tableaux et des objets n’a pas la même syntaxe, mais a le même objectif. Il est possible d’affecter une valeur par défaut aux variables.
Exemple (objet) :
const { firstName, age = 34 } = { firstName: "Evelyn" };
// Log: Evelyn
console.log(firstName);
// Log: 34
console.log(age);...
Énumération
Les énumérations (appelées également enum) permettent de définir un groupe de valeurs constantes. Elles ont pour but premier d’empêcher l’utilisation de chaînes de caractères ou de chiffres directement dans le code. On parle de chaînes ou chiffres magiques lorsque ceux-ci n’ont aucune signification parlante. Ils entravent la lisibilité et la compréhension du code.
Exemple :
function fnTreatStatus(status: number) {
if (status === 1) {
//...
} else if (status === 2) {
//...
}
}
Dans cet exemple, il est impossible de comprendre à quoi correspondent les nombres 1 et 2. Manipuler ce genre de chaînes de caractères (ou de chiffres) directement dans le code sans utiliser de constantes peut poser des soucis, notamment en cas de réusinage du code. Dans ce genre de cas, il est préférable d’utiliser une énumération.
Pour créer des énumérations en TypeScript, il faut utiliser le mot-clé enum. Il existe deux types d’énumération : les énumérations numériques et les énumérations littérales.
Exemple (numérique) :
enum Status {
InOffice,
Remote,
Off
}
function fnTreatStatus(status: Status) {
switch (status) {
case Status.InOffice:
// ...
break;
case Status.Remote:
// ...
break;
case Status.Off:
// ...
break;
}
}
Exemple (littérale) :
enum Rank {
Head = "Head",
ServicesDirector = "Services Director",
FinancialDirector = "Financial Director"
}
function fnPromote(rank: Rank) {
switch (rank) {
...
Conditionnelle If...Else
En TypeScript, la syntaxe du if...else et du switch est similaire à la grande majorité des langages de programmation. Cependant, il existe une particularité qui va influer sur l’évaluation des conditions : les valeurs évaluées à false (dites falsy) et les valeurs évaluées à true (dites truthy).
Les valeurs suivantes, évaluées dans une condition, retourneront toujours false :
-
false
-
0 (zéro)
-
’’
-
"" (chaîne vide)
-
null
-
undefined
-
NaN
Les autres valeurs seront évaluées comme true.
Exemple :
const employeeTruthy = {
firstName: "Evelyn",
age: 42,
isOff: true
}
const employeeFalsy = {
firstName: undefined,
age: 0,
isOff: false
}
if (employeeTruthy.firstName) { // true
// ...
}
if (employeeTruthy.age) { // true
// ...
}
if (employeeTruthy.isOff) { // true
// ...
}
if (employeeFalsy.firstName) { // false
// ...
}
if (employeeFalsy.age) { // false
// ...
}
if (employeeFalsy.isOff) { // false
// ...
}
Cette particularité...
Optional chaining
Lors de la création d’un objet, les propriétés non initialisées n’existent pas. L’utilisation ou l’appel d’une méthode sur une propriété non définie lève une erreur lors de l’exécution du programme. Si le type de la propriété indique que celle-ci est potentiellement non définie, le compilateur de TypeScript lève une erreur si elle est directement utilisée sans vérification préalable.
Exemple :
function fnDisplayPersonAge(person: {
firstName: string,
age?: number}
) {
// Compilation Error TS18048: 'person.age' is possibly 'undefined'
const years = person.age.toString();
console.log(`${
person.firstName
} is ${
years ? years : ""
} years old`
)
}
Pour vérifier qu’une propriété est définie, il est possible d’utiliser une condition if...else ou un opérateur ternaire.
Exemple :
function fnDisplayPersonAge(person: {
firstName: string,
age?: number}
) {
const years = person.age ? person.age.toString()...
Opérateurs
Les opérateurs sont incontournables dans les langages de programmation. Ils sont nombreux et permettent toutes sortes d’opérations, allant des opérations arithmétiques, à l’opération bit à bit, en passant par la conversion de type.
Dans ce chapitre, les opérateurs seront présentés sous forme de tableau récapitulatif. Certains (comme l’opérateur d’égalité stricte) seront présentés plus en détail dans la prochaine section (Zoom sur les opérateurs).
Dans cette section, il n’y a pas de référence aux types TypeScript. Dès lors qu’un type est mentionné, il s’agit d’un type JavaScript déterminé lors de l’interprétation du code.
1. Opérateurs arithmétiques
TypeScript permet d’utiliser les opérateurs arithmétiques classiques existant dans la grande majorité des langages de programmation.
Tableau récapitulatif des opérateurs arithmétiques :
Opérateur |
Description |
+ |
Addition |
- |
Soustraction |
* |
Multiplication |
** |
Exponentiel |
/ |
Soustraction |
% |
Reste (modulo) |
2. Opérateurs unaires
Certains opérateurs peuvent être utilisés avec deux opérandes, on parle d’opérateurs binaires. Lorsqu’ils peuvent être utilisés avec un seul opérande, on parle d’opérateurs unaires.
Tableau récapitulatif des opérateurs unaires :
Opérateur |
Description |
+ |
Convertit l’opérande en number. |
- |
Convertit l’opérande en number puis applique une négation. |
++ |
Incrémente l’opérande de 1. |
-- |
Décrémente l’opérande... |
Zoom sur les opérateurs
Dans cette section, il n’y a pas de référence aux types TypeScript. Dès lors qu’un type est mentionné, il s’agit d’un type JavaScript déterminé lors de l’interprétation du code.
1. Opérateurs unaires + et -
Les opérateurs unaires + et - convertissent une chaîne de caractères en son équivalent de type number. Par conséquent, si la chaîne est un entier, elle restera un entier, idem pour un nombre à virgule flottante.
Exemple :
// Log: 100.20
console.log(+"100.20");
// Log: -100.20
console.log(-"100.20");
// Log: 100
console.log(parseInt("100.20"));
Dans cet exemple, la dernière ligne utilise la méthode globale parseInt() qui permet de convertir une chaîne de caractères en son équivalent entier.
2. Opérateur unaire !
La double négation !! est une notation qui est souvent utilisée en TypeScript. Il s’agit d’une double utilisation de l’opérateur unaire ! qui convertit l’opérande en boolean. Le premier ! convertit l’opérande en boolean et applique une négation sur celui-ci (les valeurs dites falsy renvoient donc true et toutes les autres renvoient false). Le second ! applique une négation sur l’opérande précédemment converti pour revenir à une situation plus compréhensible (les valeurs dites falsy renvoient bien false et toutes les autres renvoient true).
Exemple :
const employee = {
firstName: "Evelyn",
lastName: "Miller",
status: 0
};
const hasStatus = !!employee.status;
// Log: false
console.log(hasStatus);
3. Opérateurs == et ===
Il est primordial de bien comprendre la particularité des égalités/inégalités strictes. Dans...
Boucles
Dans la grande majorité des langages de programmation, il existe des boucles qui permettent de répéter une séquence d’instructions. TypeScript propose le trio : for, while et do...while. Elles ont une syntaxe et un fonctionnement similaire aux autres langages de programmation. C’est pourquoi elles ne seront pas décrites dans cette section, afin de privilégier les boucles spécifiques à TypeScript.
1. Boucles for...in
En TypeScript, chaque propriété d’un objet est liée à un descripteur (qui est lui-même un objet). Celui-ci donne plusieurs informations sur une propriété : est-elle énumérable ? Est-elle modifiable ? Quelle est sa valeur ?
Une propriété est dite énumérable si son descripteur a la propriété enumerable à true.
Exemple (descripteur de propriété) :
const person = {};
Object.defineProperty(person, "firstName", {
configurable: true,
enumerable: true,
writable: true,
value: "Evelyn"
});
// Log: Evelyn
console.log(person.firstName);
La boucle for...in permet d’itérer sur les clés des propriétés énumérables d’un objet.
Syntaxe :
for (let/const key in object) { ...
Symbol
Le type Symbol est un nouveau type primitif standardisé par l’ECMAScript 2015. Il permet de représenter une donnée unique et immutable.
Syntaxe :
let/const variable = Symbol("optional description");
Exemple :
const mySymbol1 = Symbol();
const mySymbol2 = Symbol();
const mySymbol3 = Symbol(42);
const mySymbol4 = Symbol("description");
// Compilation Error TS2367: This condition will always return
// 'false' since the types 'unique symbol' and 'unique symbol'
// have no overlap
console.log(mySymbol1 === mySymbol2);
// Log : symbol
console.log(typeof mySymbol1);
Dans cet exemple, TypeScript remonte une erreur à la compilation empêchant de comparer les deux symboles. Ceux-ci étant uniques, cette comparaison retournera toujours false à l’exécution.
Les symboles peuvent être utilisés comme clés au sein d’un objet. C’est d’ailleurs leur principal cas d’utilisation. Le fait que les symboles soient uniques garantit que les clés n’entreront jamais en conflit avec d’autres clés de l’objet (chaîne de caractères ou symbole).
Exemple :
const employee = {
firstName: "Evelyn",
lastName: "Miller",
status: 0
};
const specialStatus = Symbol("status");
employee[specialStatus] = 5;
// Log : 5
console.log(employee[specialStatus]);
...
Itération et collections
1. Iterator
Certaines fonctionnalités opèrent uniquement sur des objets dits itérables. Pour qu’un objet soit itérable, il faut qu’il respecte le protocole Iterator spécifié par ECMAScript 2015. Il permet de définir la façon d’itérer sur un objet. Certains objets JavaScript l’implémentent nativement et sont donc des objets dits itérables (exemple : tableau, chaîne de caractères, Map/Set...).
Pour se conformer au protocole, un objet itérable doit posséder une méthode ayant pour clé [Symbol.iterator] qui retourne un objet appelé itérateur. L’objet itérateur doit obligatoirement avoir une méthode nommée next() qui calculera la façon d’obtenir le prochain élément de la séquence. Cette méthode doit retourner un objet contenant deux propriétés :
-
done : valeur de type boolean indiquant s’il reste des éléments dans la séquence.
-
value : valeur de la dernière itération.
Les objets n’étant pas tous nativement itérables, il est possible d’implémenter le protocole Iterator afin qu’ils le deviennent.
Exemple (for...of avec itération personnalisée) :
const iterableEmployees = {
[Symbol.iterator]() {
let counter = 0;
const iterator = {
next() {
counter++;
if (counter === 1) {
return { value: "Evelyn", done: false };
} else if (counter === 2) {
return { value: "Patrick", done: false };
} else { ...
Fonctions
Les fonctions existent dans la grande majorité des langages de programmation. En TypeScript, elles possèdent un certain nombre de spécificités et leur maîtrise devient indispensable pour faire de la programmation fonctionnelle (cf. chapitre TypeScript et la programmation fonctionnelle).
1. Les bases
Il est possible de déclarer une fonction de deux façons :
Syntaxe (standard) :
function functionName(param1: type, param2: type, ...): type {
// ...
}
functionName (arg1, argN);
Syntaxe (affectation dans une variable) :
const functionAlias = function functionName(
param1: type,
param2: type,
...
): type {
// ...
}
functionAlias (arg1, argN);
Dans la deuxième syntaxe, la variable fnAlias va obtenir une référence qui pointe sur la zone mémoire où la fonction fn est déclarée. Les fonctions déclarées de la sorte présentent des avantages si elles sont utilisées en paramètre d’autres fonctions (cf. chapitre TypeScript et la programmation fonctionnelle), ou dans le cas des closures (fermetures en français, cf. section Les closures).
TypeScript ne peut pas déduire le type des paramètres de fonctions, il est donc nécessaire de les préciser. Toutefois, le type de retour des fonctions peut être inféré.
Exemple (Visual Studio Code) :
Les paramètres d’une fonction peuvent avoir une valeur par défaut.
Exemple :
function isEmployable(hasResume = false, age = 18) {
return hasResume && age >= 18;
}
isEmployable();
isEmployable(true);
isEmployable(true, 20);
Lors de la déclaration d’un paramètre ayant une valeur par défaut, il est inutile de préciser le type du paramètre. Le compilateur est capable de l’inférer.
Les paramètres d’une fonction peuvent être définis comme optionnels en rajoutant le caractère "?" après le nom du paramètre.
Exemple :
function isEmployable(hasResume = false, age?: number) {
return hasResume && age && age >= 18;
}
isEmployable();
isEmployable(true);
isEmployable(true, 20);
Seule...
Prototype
En TypeScript, tout objet possède une propriété qui contient un lien vers un autre objet appelé prototype (nommé __proto__). Ce même objet prototype contiendra un lien vers un autre objet prototype et ainsi de suite, d’où l’appellation de chaîne de prototypes.
Ces objets prototypes possèdent des propriétés et des méthodes qui sont partagées entre tous les objets qui les référencent comme prototypes (via leur chaîne de prototypes). Par conséquent, chaque objet possède des méthodes/propriétés qui lui sont propres et des méthodes/propriétés appartenant aux objets prototypes faisant partie de sa chaîne. Lorsque l’interpréteur JavaScript tente de résoudre un appel à une propriété, il commence par chercher dans les propriétés propres à l’objet puis remonte la chaîne de prototypes jusqu’à trouver la propriété.
Tout objet référence au minimum un prototype dans sa chaîne : Object.prototype. Il contient les méthodes et propriétés propres aux objets. C’est pour cela qu’il est régulièrement dit qu’en TypeScript tout est objet. Les tableaux, les objets littéraux, les fonctions, mais aussi...
Gestion des exceptions
En TypeScript, il existe le bloc try/catch/finally qui permet de gérer les exceptions survenant au moment de l’exécution du programme.
Syntaxe :
try {
//...
} catch (e) {
//...
} finally {
//...
}
Exemple :
try {
openSqlConnection()
} catch (e) {
console.log(‘Error while trying to open SQL connection', e);
} finally {
closeSqlConnection();
}
Le bloc try/catch ne fonctionne qu’au moment de l’exécution. Par conséquent, si la syntaxe du bloc est invalide, il ne fonctionnera pas.
Il est possible de déclencher des exceptions à l’aide du mot-clé throw.
Syntaxe :
throw new Error("message");
Exemple :
setEmployeeName(firstName : string) {
if(typeof firstName !== ‘string' {
throw new Error(‘invalid parameter');
}
}
L’instance de la classe Error conserve l’ensemble des informations qui permettent de localiser l’origine de l’exception.
Le concept de classe sera abordé dans le chapitre La programmation orientée objet.
Toutefois, il est possible de déclencher une exception sans créer une nouvelle instance de la classe Error :
throw...