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
- Préférez les interfaces pour définir la forme des objets
- Utilisez
type
pour les unions, intersections et alias - Nommez clairement vos interfaces et types
- Utilisez les génériques pour la réutilisabilité
- 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