TypeScript : Notes sur la Documentation Officielle

TypeScript : Notes sur la Documentation Officielle

Déclaration de Variables

TypeScript recommande l'utilisation de let plutôt que var. La syntaxe de TypeScript est une extension de celle de JavaScript.

Types de Base

TypeScript introduit le typage statique.


let variableTexte: string = 'Initialisation';
   

Tableaux

Les génériques permettent de contraindre le type des éléments d'un tableau, ce qui est une fonctionnalité très utile.


// Tableau de chaînes de caractères
let listeChaines: Array<string> = ['un', 'deux', 'trois'];

// Tableau avec types mixtes (à utiliser avec précaution, ou spécifier 'any')
let listeMixte: (number | string)[] = [1, 'deux', 'trois'];

// Tableau de n'importe quel type
let listeAny: any[] = [1, 'deux', true];
let listeAny2: Array<any> = [1, 'deux', false]; // Equivalent
   

Déstructuration

La déstructuration est une fonctionnalité puissante pour extraire des valeurs de tableaux ou d'objets. Bien que pratique, elle peut parfois rendre le code moins lisible si elle est utilisée de manière excessive.


let objetSource = {
   proprieteA: "valeurA",
   proprieteB: 12,
   proprieteC: "valeurC"
};

let { proprieteA, proprieteB } = objetSource;

// Déstructuration dans les paramètres de fonction
interface Configuration { a: string, b?: number }
function traiterObjet({ a, b }: Configuration): void {
   console.log(`Propriété a: ${a}, Propriété b: ${b}`);
}
   

Opérateur de Propagation (Spread Operator)

L'opérateur de propagation (...) permet d'étendre des éléments d'un tableau ou des propriétés d'un objet dans un nouveau tableau ou objet.


// Propagation dans les tableaux
let debutTableau = [1, 2];
let finTableau = [3, 4];
let tableauComplet = [0, ...debutTableau, ...finTableau, 5];
console.log(tableauComplet); // [ 0, 1, 2, 3, 4, 5 ]

// Propagation dans les objets
let valeursParDefaut = { nourriture: "épicé", prix: "$$", ambiance: "bruyant" };
let recherchePersonnalisee = { ...valeursParDefaut, nourriture: "riche" };
console.log(recherchePersonnalisee); // { nourriture: 'riche', prix: '$$', ambiance: 'bruyant' }
   

Interfaces

Les interfaces définissent la structure des objets. Elles spécifient les propriétés et leurs types. Les propriétés marquées comme optionnelles avec ? peuvent être omises.

Définition d'une Interface


interface ElementEtiquete {
 label: string;
}

function afficherEtiquette(objetEtiquete: ElementEtiquete) {
 console.log(objetEtiquete.label);
}

let monObjet = { taille: 10, label: "Objet de taille 10" };
afficherEtiquette(monObjet); // Affiche "Objet de taille 10"
   

Propriétés Optionnelles

Utilisez ? pour marquer une propriété comme optionnelle.


interface ConfigurationCarre {
 couleur?: string;
 largeur?: number;
}
   

Propriétés Lecture Seule

Le mot-clé readonly permet de définir des propriétés qui ne peuvent être modifiées après leur initialisation.


interface Point {
   readonly x: number;
   readonly y: number;
}

let p1: Point = { x: 10, y: 20 };
// p1.x = 5; // Erreur: Ne peut pas assigner à 'x' car c'est une propriété readonly.
   

Différence entre readonly et const

const est utilisé pour les variables dont la référence ne doit pas changer. readonly est utilisé pour les propriétés d'objets ou d'autres structures de données qui ne doivent pas être modifiées après initialisation.

Interfaces de Fonction

Une interface peut décrire la signature d'une fonction.


interface EtudiantFormateur {
   (nom: string, age: number): string;
}

let creerDescription: EtudiantFormateur = function(nomEtudiant: string, ageEtudiant: number): string {
   return `Je suis ${nomEtudiant}, j'ai ${ageEtudiant} ans.`;
};

console.log(creerDescription("Alice", 25)); // Je suis Alice, j'ai 25 ans.
   

Types Indexables

Les types indexables permettent de décrire les types des clés et des valeurs d'un objet, souvent utilisés pour représenter des dictionnaires ou des tableaux typés.


interface TableauChaines {
 [index: number]: string; // Les index sont des nombres, les valeurs sont des chaînes.
}

let monTableau: TableauChaines;
monTableau = ["Alice", "Bob"];
let premierElement: string = monTableau[0]; // 'Alice'

interface DictionnaireNombres {
 [key: string]: number; // Les clés sont des chaînes, les valeurs sont des nombres.
 longueur: number; // Une propriété supplémentaire doit aussi être de type 'number'.
}

let dico: DictionnaireNombres = {
 'a': 1,
 'b': 2,
 longueur: 2
};
   

Restriction sur les types indexables : Lorsqu'une interface a des index numériques et des index de chaînes, la signature du type de chaîne doit être une sous-signature du type de l'index numérique. Ceci est dû au fait que les noms de propriétés JavaScript sont toujours des chaînes.


// Ceci est correct car 'Dog' est une sous-signature de 'Animal' (un chien est un animal).
interface FourreToutCorrect {
   [index: number]: Animal;
   [index: string]: Dog;
}

// Ceci génère une erreur car un 'Dog' n'est pas nécessairement un 'string'.
// Le type retourné par l'index de chaîne doit pouvoir être assigné au type retourné par l'index de nombre.
interface FourreToutIncorrect {
   [index: number]: Dog;
   [index: string]: Animal; // Erreur: Le type 'Animal' est trop large pour l'index de chaîne.
}
   

Classes

Les classes en TypeScript sont similaires à celles de JavaScript ES6, mais avec des fonctionnalités de typage statique et des modificateurs d'accès.

Implémentation d'Interfaces

Une classe peut implémenter une interface, garantissant que la classe respecte la structure définie par l'interface.


interface Personne {
   nom: string;
   age: number;
}

class Etudiant implements Personne {
   constructor(public nom: string, public age: number) {}

   afficher(): void {
       console.log(`Nom: ${this.nom}, Âge: ${this.age}`);
   }
}

let etudiant1 = new Etudiant("Bob", 20);
etudiant1.afficher();
   

Types Statiques et d'Instance

Une classe possède deux types : le type de ses membres statiques (y compris le constructeur) et le type de ses instances. L'implémentation d'une interface ne vérifie que le type de l'instance.


interface ConstructeurHorloge {
   new (heure: number, minute: number): InstanceHorloge;
}

interface InstanceHorloge {
   tick(): void;
}

function creerHorloge(constructeurHorloge: ConstructeurHorloge, heure: number, minute: number): InstanceHorloge {
   return new constructeurHorloge(heure, minute);
}

class HorlogeModerne implements InstanceHorloge {
   constructor(public heure: number, public minute: number) {}
   tick() {
       console.log("Tik Tok");
   }
}

let monHorloge: InstanceHorloge = creerHorloge(HorlogeModerne, 10, 30);
monHorloge.tick();
   

Héritage d'Interfaces

Une interface peut hériter d'une ou plusieurs autres interfaces, combinant leurs membres.


interface Animal {
   nom: string;
   manger(): void;
}

interface Chien extends Animal {
   aboyer(): void;
}

let monChien: Chien = {
   nom: "Buddy",
   manger: () => console.log("Miam"),
   aboyer: () => console.log("Ouaf")
};
   

Types Hybrides

Une entité peut combiner des caractéristiques d'objet et de fonction.


interface ElementInteragissable {
   (element: string): void; // Signature de fonction
   elementSource?: string;    // Propriété optionnelle
   fermer(): void;            // Méthode
}

function creerElement(): ElementInteragissable {
   let element = function(element: string) {
       console.log(`Action sur : ${element}`);
       (element as any).elementSource = element; // Accès non typé pour l'exemple
   } as ElementInteragissable;

   element.fermer = function() {
       console.log("Fermé");
   };
   return element;
}

let interagisseur = creerElement();
interagisseur("Fichier");
interagisseur.fermer();
   

Héritage de Classes

Les classes peuvent hériter d'autres classes en utilisant le mot-clé extends. Les classes dérivées peuvent surcharger les méthodes de leur classe parente.


class Animal {
   constructor(public nom: string) {}
   deplacer(distance: number = 0) {
       console.log(`${this.nom} s'est déplacé de ${distance}m.`);
   }
}

class Serpent extends Animal {
   constructor(nom: string) { super(nom); }
   deplacer(distance: number = 5) {
       console.log("Glisse...");
       super.deplacer(distance);
   }
}

let sam = new Serpent("Sammy");
sam.deplacer(); // Glisse... Sammy s'est déplacé de 5m.
   

Constructeurs de Classes Dérivées

Le constructeur d'une classe dérivée doit toujours appeler super() pour initialiser la partie parente de l'objet.


class Parent {
   nom: string = "Parent";
   constructor() {
       console.log(`Constructeur Parent: ${this.nom}`);
   }
}

class Enfant extends Parent {
   constructor() {
       super(); // Doit être appelé avant l'utilisation de 'this'
       console.log(`Constructeur Enfant`);
   }
}
let enfant = new Enfant(); // Affiche les messages des deux constructeurs.
   

Modificateurs d'Accès (public, private, protected)

Ces mots-clés contrôlent la visibilité des membres d'une classe.

  • public : Accessible de partout (par défaut).
  • private : Accessible uniquement à l'intérieur de la classe qui le définit.
  • protected : Accessible à l'intérieur de la classe qui le définit et de ses sous-classes.

Propriétés Lecture Seule dans les Classes

Similaire à readonly dans les interfaces, cela empêche la modification après initialisation.


class ExempleLectureSeule {
   readonly identifiant: string;
   constructor(id: string) {
       this.identifiant = id;
   }
}
   

Accesseurs (Getters et Setters)

Permettent de contrôler l'accès aux propriétés d'une classe, offrant des opportunités de validation ou de logique calculée.


let motDePasseSecret = "123";

class Employe {
   private _nomComplet: string = "";

   get nomComplet(): string {
       return this._nomComplet;
   }

   set nomComplet(nom: string) {
       if (motDePasseSecret === "123") {
           this._nomComplet = nom;
       } else {
           console.error("Mise à jour non autorisée !");
       }
   }
}

let employe = new Employe();
employe.nomComplet = "Jean Dupont"; // Utilise le setter
console.log(employe.nomComplet);     // Utilise le getter
   

Propriétés Statiques

Les membres static appartiennent à la classe elle-même plutôt qu'à ses instances. Ils sont accessibles directement via le nom de la classe.


class Configurateur {
   static version: string = "1.0";
   static getVersion(): string {
       return Configurateur.version;
   }
}
console.log(Configurateur.getVersion()); // 1.0
   

Classes Abstraites

Les classes abstraites définissent des structures de base qui doivent être implémentées par les classes dérivées. Elles ne peuvent pas être instanciées directement.


abstract class Departement {
   constructor(public nom: string) {}

   imprimerNom(): void {
       console.log(`Nom du département : ${this.nom}`);
   }

   abstract imprimerReunion(): void; // Doit être implémenté par les sous-classes
}

class DepartementComptabilite extends Departement {
   constructor() {
       super('Comptabilité');
   }

   imprimerReunion(): void {
       console.log('Le département comptabilité se réunit le lundi.');
   }
}

let dept: Departement;
// dept = new Departement("Test"); // Erreur: Ne peut pas instancier une classe abstraite.
dept = new DepartementComptabilite();
dept.imprimerReunion();
   

Types Avancés

Types de Union

Un type de union permet à une variable d'accepter plusieurs types.


interface Oiseau {
   voler(): void;
   pondreOeufs(): void;
}

interface Poisson {
   nager(): void;
   pondreOeufs(): void;
}

function obtenirPetitAnimal(): Oiseau | Poisson {
   // Logique pour retourner soit un Oiseau, soit un Poisson
   if (Math.random() > 0.5) {
       return { voler: () => {}, pondreOeufs: () => {} } as Oiseau;
   } else {
       return { nager: () => {}, pondreOeufs: () => {} } as Poisson;
   }
}

let petitAnimal = obtenirPetitAnimal();

// On ne peut accéder qu'aux membres communs aux deux types
petitAnimal.pondreOeufs();

// Pour accéder aux membres spécifiques, il faut un garde de type (type guard)
if ("voler" in petitAnimal) {
   petitAnimal.voler();
} else {
   petitAnimal.nager();
}
   

Gardiens de Type (Type Guards)

Les gardiens de type sont des expressions qui garantissent un type spécifique à l'intérieur d'un certain scope.

Gardiens de Type Personnalisés


function estPoisson(animal: Oiseau | Poisson): animal is Poisson {
   return (animal as Poisson).nager !== undefined;
}

let monAnimal = obtenirPetitAnimal();

if (estPoisson(monAnimal)) {
   monAnimal.nager(); // Ici, monAnimal est typé comme Poisson
} else {
   monAnimal.voler(); // Ici, monAnimal est typé comme Oiseau
}
   

Gardiens de Type typeof

Utilisent l'opérateur typeof pour vérifier les types primitifs.


function formaterValeur(valeur: string | number): string {
   if (typeof valeur === "string") {
       return valeur.toUpperCase();
   } else if (typeof valeur === "number") {
       return valeur.toFixed(2);
   }
   // Gère le cas où ni string ni number (bien que le type le restreigne)
   throw new Error("Type invalide");
}
   

Gardiens de Type instanceof

Utilisent l'opérateur instanceof pour vérifier si une instance appartient à une classe spécifique.


class Chat { miauler() {} }
class Chien { aboyer() {} }

function faireBruit(animal: Chat | Chien) {
   if (animal instanceof Chat) {
       animal.miauler();
   } else {
       animal.aboyer();
   }
}
   

Alias de Type (Type Aliases)

Permettent de créer un nouveau nom pour un type existant, améliorant la lisibilité et la réutilisabilité.


type Nom = string;
type NomOuResolver = Nom | (() => Nom);

function obtenirNom(n: NomOuResolver): Nom {
   if (typeof n === 'string') {
       return n;
   } else {
       return n();
   }
}

type Conteneur<T> = { valeur: T }; // Alias générique
   

Types Union Identifiables (Discriminated Unions)

Une forme spécifique de type union où chaque membre de l'union possède une propriété commune (le discriminateur) avec une valeur littérale unique, permettant une vérification de type sûre dans les blocs switch.


interface Carre {
   type: "carre";
   taille: number;
}
interface Rectangle {
   type: "rectangle";
   largeur: number;
   hauteur: number;
}
type Forme = Carre | Rectangle;

function calculerAire(f: Forme): number {
   switch (f.type) {
       case "carre": return f.taille * f.taille;
       case "rectangle": return f.largeur * f.hauteur;
   }
}
   

Types Génériques

Les génériques permettent d'écrire du code qui peut fonctionner avec différents types tout en conservant la sécurité de type.


class Createur<T> {
   constructor(private createur: new () => T) {}
   creerInstance(): T {
       return new this.createur();
   }
}

class MonType { nom: string = "MonType"; }

let createurMonType = new Createur<MonType>(MonType);
let instance = createurMonType.creerInstance();
console.log(instance.nom); // MonType
   

Types Indexables (Rappel)

Les types indexables permettent de définir la structure des objets utilisés comme des dictionnaires ou des tableaux.


function extraireValeurs<T, K extends keyof T>(objet: T, noms: K[]): T[K][] {
   return noms.map(nom => objet[nom]);
}

interface Utilisateur {
   nom: string;
   age: number;
}

let utilisateur: Utilisateur = { nom: "Alice", age: 30 };
let nomsUtilisateur: string[] = extraireValeurs(utilisateur, ['nom']); // ['Alice']
   

Types Mappés

Les types mappés sont une fonctionnalité avancée qui permet de créer de nouveaux types en transformant les propriétés d'un type existant.


// Rend toutes les propriétés d'un type 'readonly'
type Readonly<T> = {
   readonly [P in keyof T]: T[P];
};

// Rend toutes les propriétés d'un type optionnelles
type Partial<T> = {
   [P in keyof T]?: T[P];
};

type UtilisateurPartiel = Partial<Utilisateur>; // { nom?: string, age?: number }
   

Symboles

Les symboles sont des types primitifs uniques et immuables, souvent utilisés comme clés de propriété d'objet pour éviter les collisions de noms.


const monSymbole = Symbol("description");
let obj = {
   [monSymbole]: "valeur"
};
console.log(obj[monSymbole]); // valeur
   

Itérateurs et Générateurs

Les itérateurs permettent de parcourir des collections de données. Les générateurs sont des fonctions spéciales qui peuvent suspendre leur exécution.


let tableauSimple = [1, "texte", true];

// for...of itère sur les valeurs
for (const element of tableauSimple) {
   console.log(element);
}

// for...in itère sur les clés (index)
for (const index in tableauSimple) {
   console.log(index);
}
   

Modules

Les modules permettent d'organiser le code en fichiers séparés, gérant les dépendances entre eux via les instructions import et export.


// Fichier: utilitaires.ts
export function additionner(a: number, b: number): number {
   return a + b;
}

// Fichier: principal.ts
import { additionner } from './utilitaires';

console.log(additionner(5, 3)); // 8
   

Espaces de Nommage (Namespaces)

Les espaces de nommage fournissent un moyen de regrouper loigquement du code et d'éviter les conflits de noms, similaires aux modules mais avec une portée différente.


// Fichier: geometrie.ts
namespace Geometrie {
   export interface Point { x: number; y: number; }
   export class Cercle {
       constructor(public centre: Point, public rayon: number) {}
       aire(): number { return Math.PI * this.rayon ** 2; }
   }
}

// Fichier: forme.ts
/// <reference path="geometrie.ts" /> // Directive pour inclure le namespace
let monCercle = new Geometrie.Cercle({ x: 0, y: 0 }, 10);
console.log(monCercle.aire());
   

Fusion de Déclarations (Declaration Merging)

Lorsque plusieurs déclarations du même nom existent (par exemple, plusieurs interfaces avec le même nom), TypeScript les fusionne en une seule déclaration.


interface Fenetre {
   titre: string;
}
interface Fenetre {
   fermer(): void;
}

// Les deux interfaces sont fusionnées en une seule :
// interface Fenetre { titre: string; fermer(): void; }
   

JSX

TypeScript prend en charge la syntaxe JSX, couramment utilisée avec des bibliothèques comme React, en permettant sa compilation en JavaScript valide.

Décorateurs

Les décorateurs sont une fonctionnalité expérimentale (ES.Next) qui permet d'annoter ou de modifier des classes, des méthodes, des propriétés ou des paramètres.


function logDecorateur(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
   const originalMethod = descriptor.value;
   descriptor.value = function(...args: any[]) {
       console.log(`Appel de la méthode : ${propertyKey} avec les arguments : ${args}`);
       const result = originalMethod.apply(this, args);
       console.log(`La méthode ${propertyKey} a retourné : ${result}`);
       return result;
   };
   return descriptor;
}

class Calculatrice {
   @logDecorateur
   ajouter(a: number, b: number): number {
       return a + b;
   }
}

const calc = new Calculatrice();
calc.ajouter(5, 3);
   

Fonctions d'aide (Mixins)

Permettent de composer des classes à partir de plusieurs sources, simulant l'héritage multiple.


function AppliquerMixins(classeDerivee: any, constructeursBases: any[]) {
   constructeursBases.forEach(baseCtor => {
       Object.getOwnPropertyNames(baseCtor.prototype).forEach(nom => {
           Object.defineProperty(classeDerivee.prototype, nom,
               Object.getOwnPropertyDescriptor(baseCtor.prototype, nom) || Object.create(null));
       });
   });
}

class Plongeur {
   plonger() { console.log("Je plonge !"); }
}
class Nageur {
   nager() { console.log("Je nage !"); }
}

class Amphibien implements Plongeur, Nageur {
   plonger!: () => void;
   nager!: () => void;
}

AppliquerMixins(Amphibien, [Plongeur, Nageur]);

const creature = new Amphibien();
creature.plonger();
creature.nager();
   

Instructions Triple-Slash

Les directives triple-slash (/// <reference ... />) sont utilisées pour référencer d'autres fichiers de déclaration (.d.ts) ou de code source TypeScript.

Vérification de Type dans les Fichiers JavaScript

TypeScript peut analyser des fichiers JavaScript et fournir une vérification de type si des commentaires JSDoc appropriés sont présents.

Surcharge de Fonctions (Function Overloads)

Permet de définir plusieurs signatures pour une même fonction, TypeScript choisissant la signature la plus spécifique correspondante à l'appel.


// Les déclarations les plus spécifiques doivent précéder les plus générales.
declare function fn(element: HTMLDivElement): string;
declare function fn(element: HTMLElement): number;
declare function fn(element: any): any;

let divElement: HTMLDivElement;
let resultat: string = fn(divElement); // Utilise la première surcharge.
   

Paramètres Optionnels

Utilisez ? après le nom du paramètre pour le rendre optionnel. Les paramètres optionnels doivent apparaître après les paramètres obligatoires.


function construireNom(prenom: string, nomFamille?: string): string {
   if (nomFamille) {
       return `${prenom} ${nomFamille}`;
   }
   return prenom;
}
   

Étiquettes: TypeScript JavaScript programmation typemaille Développement Web

Publié le 27 juin à 02h24