Applications pratiques de TypeScript

Introduction à TypeScript

TypeScript étend JavaScript en intégrant un système de types statiques. Il offre une détection précoce des erreurs, une assistance améliorée dans les IDE et une maintenabilité accrue pour les projets d'envergure. Un environnement Node.js (version 14 ou supérieure) est requis.

Démarrage rapide

Installer TypeScript

npm install -g typescript  # Installation globale du compilateur
tsc --version             # Afficher la version

Créer un premier fichier TypeScript

Créez un fichier bonjour.ts :

function afficherMessage(nom: string): void {
  console.log(`Salut, ${nom} !`);
}
afficherMessage("Monde");

Compiler et exécuter

tsc bonjour.ts    # Génère bonjour.js
node bonjour.js   # Affiche "Salut, Monde !"

Fondamentaux de TypeScript

Types de base

let termine: boolean = false;
let quantite: number = 42;
let nom: string = "Alice";

// Tableaux
let chiffres: number[] = [1, 2, 3];
let mots: Array<string> = ["bonjour", "monde"];

// Tuple (longueur et types fixes)
let coordonnees: [string, number] = ["Alice", 30];

// Énumération
enum Couleur { Rouge, Vert, Bleu }
let couleurPreferee: Couleur = Couleur.Vert;

// Types any et unknown
let inconnu: any = 4;
inconnu = "peut-être une chaîne";

// void (fonction sans valeur de retour)
function journal(): void {
  console.log("Pas de retour");
}

Interfaces

Définir la structure d'un objet :

interface Utilisateur {
  nom: string;
  age: number;
  email?: string;  // Propriété optionnelle
}

function afficherUtilisateur(utilisateur: Utilisateur): void {
  console.log(`Nom: ${utilisateur.nom}, Age: ${utilisateur.age}`);
}

const bob: Utilisateur = { nom: "Bob", age: 25 };
afficherUtilisateur(bob);

Classes

Support de la programmation orientée objet :

class Creature {
  private nom: string;

  constructor(nom: string) {
    this.nom = nom;
  }

  public seDeplacer(distance: number = 0): void {
    console.log(`${this.nom} s'est déplacé de ${distance}m.`);
  }
}

class Chien extends Creature {
  aboyer(): void {
    console.log("Ouaf ! Ouaf !");
  }
}

const monChien = new Chien("Rex");
monChien.seDeplacer(10);
monChien.aboyer();

Types de fonctions

type Operation = (a: number, b: number) => number;

const addition: Operation = (x, y) => x + y;
console.log(addition(2, 3));  // 5

// Paramètres optionnels et par défaut
function saluer(nom: string, message: string = "Bonjour"): string {
  return `${message}, ${nom} !`;
}
console.log(saluer("Alice"));  // "Bonjour, Alice !"

Génériques

// Fonction générique
function identifier<T>(valeur: T): T {
  return valeur;
}
let resultat1 = identifier<string>("hello");
let resultat2 = identifier(42);

// Interface générique
interface Paire<K, V> {
  cle: K;
  valeur: V;
}
const paireExemple: Paire<number, string> = { cle: 1, valeur: "Pomme" };

Types union et garde de type

type Identifiant = string | number;

function afficherId(id: Identifiant) {
  if (typeof id === "string") {
    console.log(`ID chaîne: ${id.toUpperCase()}`);
  } else {
    console.log(`ID numérique: ${id.toFixed(2)}`);
  }
}

afficherId("abc123");
afficherId(3.1415);

Configuration TypeScript (tsconfig.json)

{
  "compilerOptions": {
    "target": "ES6",
    "module": "CommonJS",
    "strict": true,
    "outDir": "./dist",
    "esModuleInterop": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

Utilisation avec React

// Props et State typés pour les composants
interface CompteurProps {
  valeurInitiale: number;
}

interface CompteurState {
  compteur: number;
}

class Compteur extends React.Component<CompteurProps, CompteurState> {
  state: CompteurState = {
    compteur: this.props.valeurInitiale,
  };

  incrementer = () => {
    this.setState({ compteur: this.state.compteur + 1 });
  };

  render() {
    return (
      <div>
        <p>Compteur: {this.state.compteur}</p>
        <button onClick={this.incrementer}>Incrémenter</button>
      </div>
    );
  }
}

// Composant fonctionnel
type BoutonProps = {
  onClick: () => void;
  enfants: React.ReactNode;
};

const Bouton: React.FC<BoutonProps> = ({ onClick, enfants }) => (
  <button onClick={onClick}>{enfants}</button>
);

Questions fréquentes

Quelle différence entre TypeScript et JavaScript ?

TypeScript est un langage à types statiques avec vérification à la compilation ; JavaScript est à types dynamiques.

Comment gérer les bibliothèques tierces sans définitions de types ?

Instalelz les paquets de types communautaires : npm install @types/nom-bibliotheque. Ou déclarez manuellement dans un fichier .d.ts avec declare module 'nom-bibliotheque';.

Comment migrer progressivement un projet JavaScript vers TypeScript ?

1. Renommez les fichiers en .ts et corrigez les erreurs de type. 2. Configurez allowJs: true dans tsconfig.json pour une compilation mixte.

Applications

Applications frontend

Props de composants avec sécurité de type

interface BoutonProps {
  enfants: React.ReactNode;
  onClick: () => void;
  variante?: "primaire" | "secondaire";
  desactive?: boolean;
}

const Bouton: React.FC<BoutonProps> = ({
  enfants,
  onClick,
  variante = "primaire",
  desactive = false
}) => {
  return (
    <button
      className={`btn-${variante}`}
      onClick={onClick}
      disabled={desactive}
    >
      {enfants}
    </button>
  );
};

// Utilisation avec vérification des types
<Bouton variante="primaire" onClick={() => console.log("Cliqué")}>
  Valider
</Bouton>

Gestion d'état (Redux + TypeScript)

// Définition des types d'action
type ActionCompteur =
  | { type: 'INCREMENTER'; payload: number }
  | { type: 'DECREMENTER'; payload: number };

// Définition du type d'état
interface EtatCompteur {
  compteur: number;
}

// Fonction reducer
const reducerCompteur = (
  etat: EtatCompteur = { compteur: 0 },
  action: ActionCompteur
): EtatCompteur => {
  switch (action.type) {
    case 'INCREMENTER':
      return { compteur: etat.compteur + action.payload };
    case 'DECREMENTER':
      return { compteur: etat.compteur - action.payload };
    default:
      return etat;
  }
};

Applications backend

Routes Express avec sécurité de type

import express, { Request, Response } from 'express';

interface Personne {
  id: number;
  nom: string;
  courriel: string;
}

const application = express();
application.use(express.json());

// GET /personnes/:id
application.get('/personnes/:id', (req: Request<{ id: string }>, res: Response<Personne>) => {
  const personneId = parseInt(req.params.id);
  const personne: Personne = { id: personneId, nom: "Alice", courriel: "alice@exemple.com" };
  res.json(personne);
});

// POST /personnes
application.post('/personnes', (req: Request<{}, {}, Personne>, res: Response<{ succes: boolean }>) => {
  const nouvellePersonne = req.body;
  console.log("Création de personne:", nouvellePersonne);
  res.json({ succes: true });
});

Opérations de base de données (TypeORM + TypeScript)

import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from 'typeorm';

@Entity()
class Personne extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  nom: string;

  @Column({ unique: true })
  courriel: string;

  static async chercherParNom(nom: string): Promise<Personne | undefined> {
    return this.findOne({ where: { nom } });
  }
}

// Utilisation
const personne = await Personne.chercherParNom("Alice");

Développement d'outils

Gestion de configuration avec sécurité de type

interface ConfigApp {
  port: number;
  bd: {
    hote: string;
    utilisateur: string;
    motDePasse: string;
  };
  environnement: "developpement" | "production";
}

const chargerConfig = (): ConfigApp => {
  return {
    port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
    bd: {
      hote: process.env.DB_HOST || "localhost",
      utilisateur: process.env.DB_USER || "root",
      motDePasse: process.env.DB_PASSWORD || "",
    },
    environnement: process.env.NODE_ENV === "production" ? "production" : "developpement",
  };
};

const config = chargerConfig();
console.log(config.bd.hote);

Fonctions utilitaires génériques

// Type de réponse paginée
interface ReponsePaginee<T> {
  donnees: T[];
  page: number;
  total: number;
}

// Fonction générique pour la pagination
function paginer<T>(elements: T[], page: number, taillePage: number): ReponsePaginee<T> {
  const debut = (page - 1) * taillePage;
  const fin = debut + taillePage;
  return {
    donnees: elements.slice(debut, fin),
    page,
    total: elements.length,
  };
}

// Exemple d'utilisation
const utilisateurs = [{ id: 1, nom: "Alice" }, { id: 2, nom: "Bob" }];
const resultat = paginer(utilisateurs, 1, 1);

Opérations avancées sur les types

Garde de type

interface Chat {
  type: "chat";
  miauler: () => void;
}

interface Chien {
  type: "chien";
  aboyer: () => void;
}

type Animal = Chat | Chien;

function gererAnimal(animal: Animal) {
  if (animal.type === "chat") {
    animal.miauler();
  } else {
    animal.aboyer();
  }
}

Types conditionnels et mappés

// Rendre toutes les propriétés optionnelles
type PartielTout<T> = {
  [K in keyof T]?: T[K];
};

// Type conditionnel pour extraire le type de retour
type TypeRetour<T> = T extends (...args: any[]) => infer R ? R : never;

// Exemple
function multiplier(a: number, b: number): number { return a * b; }
type TypeRetourMultiplier = TypeRetour<typeof multiplier>; // number

Tests et débogage

Tests avec sécurité de type (Jest + TypeScript)

// Fonction utilitaire à tester
function somme(a: number, b: number): number {
  return a + b;
}

describe('Fonction somme', () => {
  it('additionne correctement deux nombres', () => {
    expect(somme(2, 3)).toBe(5);
  });

  it('rejette les entrées non numériques', () => {
    // @ts-expect-error - Test intentionnel de la sécurité des types
    expect(somme("2", 3)).toThrow();
  });
});

Solutions pour scénarios courants

Gérer les bibliothèques sans définitions de types

// Créer un fichier declarations.d.ts
declare module 'bibliotheque-non-typee' {
  export function effectuerAction(config: any): void;
}

// Utilisation
import { effectuerAction } from 'bibliotheque-non-typee';
effectuerAction({ cle: "valeur" });

Objets avec propriétés dynamiques

interface ObjetDynamique {
  [cle: string]: string | number;
}

const monObjet: ObjetDynamique = {
  nom: "Alice",
  age: 30,
  // La ligne suivante provoquerait une erreur
  // estAdmin: true
};

Ressources recommandées

  • TypeScript Playground : pour expérimenter en ligne
  • Documentation des types utilitaires
  • DefinitelyTyped : dépôt de définitions de types tierces

Étiquettes: TypeScript React Redux Express TypeORM

Publié le 16 juin à 22h49