0%
Interfaces et types avancés

Interfaces et types avancés

Structurez vos données avec les interfaces

10-15 min

Interfaces et types avancés

Les interfaces et types personnalisés sont au cœur de TypeScript. Ils permettent de définir la structure de vos données et de créer des contrats clairs dans votre code.

Interfaces de base

Définition d’une interface

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

// Utilisation
let utilisateur: Utilisateur = {
  id: 1,
  nom: "Alice",
  email: "alice@example.com",
  age: 30
};

Propriétés optionnelles

interface Produit {
  id: number;
  nom: string;
  prix: number;
  description?: string; // Propriété optionnelle
  enStock?: boolean;
}

let produit1: Produit = {
  id: 1,
  nom: "Ordinateur",
  prix: 999
  // description et enStock sont optionnelles
};

let produit2: Produit = {
  id: 2,
  nom: "Souris",
  prix: 25,
  description: "Souris ergonomique",
  enStock: true
};

Propriétés en lecture seule

interface Configuration {
  readonly apiUrl: string;
  readonly version: string;
  timeout: number;
}

let config: Configuration = {
  apiUrl: "https://api.example.com",
  version: "1.0.0",
  timeout: 5000
};

// config.apiUrl = "nouvelle-url"; // ❌ Erreur : propriété en lecture seule
config.timeout = 10000; // ✅ OK

Interfaces pour les fonctions

Signature de fonction

interface CalculateurCallback {
  (a: number, b: number): number;
}

let additionner: CalculateurCallback = (x, y) => x + y;
let multiplier: CalculateurCallback = (x, y) => x * y;

// Utilisation
let resultat1 = additionner(5, 3); // 8
let resultat2 = multiplier(4, 6); // 24

Interface avec méthodes

interface Calculatrice {
  additionner(a: number, b: number): number;
  soustraire(a: number, b: number): number;
  multiplier(a: number, b: number): number;
  diviser(a: number, b: number): number;
}

class CalculatriceBasique implements Calculatrice {
  additionner(a: number, b: number): number {
    return a + b;
  }

  soustraire(a: number, b: number): number {
    return a - b;
  }

  multiplier(a: number, b: number): number {
    return a * b;
  }

  diviser(a: number, b: number): number {
    if (b === 0) {
      throw new Error("Division par zéro");
    }
    return a / b;
  }
}

Interfaces étendues

Héritage d’interfaces

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

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

interface Chat extends Animal {
  couleur: string;
  miauler(): void;
}

let monChien: Chien = {
  nom: "Rex",
  age: 3,
  race: "Labrador",
  aboyer() {
    console.log("Woof!");
  }
};

Héritage multiple

interface Volant {
  voler(): void;
  altitude: number;
}

interface Nageur {
  nager(): void;
  profondeur: number;
}

interface Canard extends Animal, Volant, Nageur {
  crier(): void;
}

let canard: Canard = {
  nom: "Donald",
  age: 2,
  altitude: 0,
  profondeur: 0,
  voler() {
    this.altitude = 100;
    console.log("Le canard vole!");
  },
  nager() {
    this.profondeur = 5;
    console.log("Le canard nage!");
  },
  crier() {
    console.log("Coin coin!");
  }
};

Types personnalisés avec type

Alias de type

type ID = string | number;
type Statut = "en_attente" | "en_cours" | "termine" | "annule";

interface Tache {
  id: ID;
  titre: string;
  statut: Statut;
  assigneA?: string;
}

let tache: Tache = {
  id: "TASK-001",
  titre: "Implémenter la fonctionnalité",
  statut: "en_cours"
};

Types union complexes

type Reponse = {
  succes: true;
  donnees: any;
} | {
  succes: false;
  erreur: string;
};

function traiterReponse(reponse: Reponse) {
  if (reponse.succes) {
    console.log("Données reçues:", reponse.donnees);
  } else {
    console.error("Erreur:", reponse.erreur);
  }
}

Types intersection

type Horodatage = {
  creeA: Date;
  modifieA: Date;
};

type Auteur = {
  auteurId: string;
  auteurNom: string;
};

type Article = {
  id: string;
  titre: string;
  contenu: string;
} & Horodatage & Auteur;

let article: Article = {
  id: "ART-001",
  titre: "Introduction à TypeScript",
  contenu: "TypeScript est...",
  creeA: new Date(),
  modifieA: new Date(),
  auteurId: "USER-001",
  auteurNom: "Alice"
};

Propriétés indexées

Index signatures

interface Dictionnaire {
  [cle: string]: string;
}

let traductions: Dictionnaire = {
  "hello": "bonjour",
  "goodbye": "au revoir",
  "thank you": "merci"
};

// Accès dynamique
let salutation = traductions["hello"]; // "bonjour"

Index avec types multiples

interface ConfigurationMixte {
  [cle: string]: string | number | boolean;
  nom: string; // Propriété spécifique requise
  version: number; // Propriété spécifique requise
}

let config: ConfigurationMixte = {
  nom: "MonApp",
  version: 1,
  debug: true,
  apiUrl: "https://api.example.com",
  timeout: 5000
};

Types conditionnels

Types conditionnels de base

type EstTableau<T> = T extends any[] ? true : false;

type Test1 = EstTableau<string[]>; // true
type Test2 = EstTableau<string>; // false
type Test3 = EstTableau<number[]>; // true

Types utilitaires conditionnels

type NonNullable<T> = T extends null | undefined ? never : T;

type Exemple1 = NonNullable<string | null>; // string
type Exemple2 = NonNullable<number | undefined>; // number
type Exemple3 = NonNullable<boolean | null | undefined>; // boolean

Types utilitaires intégrés

Partial et Required

interface UtilisateurComplet {
  id: number;
  nom: string;
  email: string;
  age: number;
  telephone?: string;
}

// Partial : toutes les propriétés deviennent optionnelles
type UtilisateurPartiel = Partial<UtilisateurComplet>;

function mettreAJourUtilisateur(
  id: number, 
  modifications: UtilisateurPartiel
): UtilisateurComplet {
  // Logique de mise à jour
  return {} as UtilisateurComplet;
}

// Required : toutes les propriétés deviennent requises
type UtilisateurRequis = Required<UtilisateurComplet>;

Pick et Omit

// Pick : sélectionner certaines propriétés
type UtilisateurPublic = Pick<UtilisateurComplet, "nom" | "email">;

// Omit : exclure certaines propriétés
type UtilisateurSansId = Omit<UtilisateurComplet, "id">;

let profilPublic: UtilisateurPublic = {
  nom: "Alice",
  email: "alice@example.com"
};

Record

type Couleur = "rouge" | "vert" | "bleu";
type CouleursHex = Record<Couleur, string>;

let couleurs: CouleursHex = {
  rouge: "#FF0000",
  vert: "#00FF00",
  bleu: "#0000FF"
};

Génériques avec interfaces

Interface générique

interface Reponse<T> {
  succes: boolean;
  donnees?: T;
  erreur?: string;
}

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

interface Produit {
  id: number;
  nom: string;
  prix: number;
}

let reponseUtilisateur: Reponse<Utilisateur> = {
  succes: true,
  donnees: { id: 1, nom: "Alice" }
};

let reponseProduits: Reponse<Produit[]> = {
  succes: true,
  donnees: [
    { id: 1, nom: "Ordinateur", prix: 999 },
    { id: 2, nom: "Souris", prix: 25 }
  ]
};

Contraintes génériques

interface AvecId {
  id: number;
}

interface Repository<T extends AvecId> {
  trouverParId(id: number): T | undefined;
  sauvegarder(entite: T): T;
  supprimer(id: number): boolean;
}

class UtilisateurRepository implements Repository<Utilisateur> {
  private utilisateurs: Utilisateur[] = [];

  trouverParId(id: number): Utilisateur | undefined {
    return this.utilisateurs.find(u => u.id === id);
  }

  sauvegarder(utilisateur: Utilisateur): Utilisateur {
    this.utilisateurs.push(utilisateur);
    return utilisateur;
  }

  supprimer(id: number): boolean {
    const index = this.utilisateurs.findIndex(u => u.id === id);
    if (index > -1) {
      this.utilisateurs.splice(index, 1);
      return true;
    }
    return false;
  }
}

Exemples pratiques

API REST typée

interface UtilisateurAPI {
  id: number;
  nom: string;
  email: string;
  creeA: string;
}

interface ReponseAPI<T> {
  donnees: T;
  meta: {
    total: number;
    page: number;
    parPage: number;
  };
}

type ReponseUtilisateurs = ReponseAPI<UtilisateurAPI[]>;
type ReponseUtilisateur = ReponseAPI<UtilisateurAPI>;

async function obtenirUtilisateurs(): Promise<ReponseUtilisateurs> {
  // Simulation d'appel API
  return {
    donnees: [
      { id: 1, nom: "Alice", email: "alice@example.com", creeA: "2024-01-01" }
    ],
    meta: { total: 1, page: 1, parPage: 10 }
  };
}

Système d’événements typé

interface EvenementMap {
  "utilisateur:cree": { utilisateur: Utilisateur };
  "utilisateur:modifie": { utilisateur: Utilisateur; modifications: Partial<Utilisateur> };
  "utilisateur:supprime": { id: number };
}

type EcouteurEvenement<T extends keyof EvenementMap> = (
  donnees: EvenementMap[T]
) => void;

class GestionnaireEvenements {
  private ecouteurs: { [K in keyof EvenementMap]?: EcouteurEvenement<K>[] } = {};

  on<T extends keyof EvenementMap>(
    evenement: T, 
    ecouteur: EcouteurEvenement<T>
  ): void {
    if (!this.ecouteurs[evenement]) {
      this.ecouteurs[evenement] = [];
    }
    this.ecouteurs[evenement]!.push(ecouteur);
  }

  emit<T extends keyof EvenementMap>(
    evenement: T, 
    donnees: EvenementMap[T]
  ): void {
    const ecouteurs = this.ecouteurs[evenement];
    if (ecouteurs) {
      ecouteurs.forEach(ecouteur => ecouteur(donnees));
    }
  }
}

// Utilisation
const gestionnaire = new GestionnaireEvenements();

gestionnaire.on("utilisateur:cree", ({ utilisateur }) => {
  console.log(`Nouvel utilisateur: ${utilisateur.nom}`);
});

Bonnes pratiques

  1. Préférez les interfaces pour définir la forme des objets
  2. Utilisez type pour les unions, intersections et alias
  3. Nommez clairement vos interfaces et types
  4. Utilisez les génériques pour la réutilisabilité
  5. Documentez vos interfaces complexes

Prochaines étapes

Avec les interfaces et types avancés maîtrisés, vous pouvez maintenant explorer :

  • Les classes et l’héritage
  • Les génériques avancés
  • Les décorateurs
  • Les modules et namespaces

Conseil : Les interfaces sont la fondation d’un code TypeScript robuste. Investissez du temps dans leur conception pour faciliter la maintenance future.

Commentaires

Les commentaires sont alimentés par GitHub Discussions

Connectez-vous avec GitHub pour participer à la discussion

Lien copié !