Leçon 3/10 10 min

Interfaces et type aliases

Décrire la forme de tes objets : interface ou type, optionnel, readonly — fautes de frappe attrapées à la compilation.

Le bug que personne ne voit en relecture

Tu as un formulaire d'inscription. L'utilisateur valide, tu envoies un email de confirmation. Sauf que l'email n'arrive jamais. Pas d'erreur, pas de crash : juste le silence. Trois jours plus tard tu fouilles le code et tu trouves ça :

const user = {
  nom: 'Sam',
  emial: 'sam@exemple.fr'   // 👈 relis bien
};

envoyerConfirmation(user.email);   // undefined : l'email ne partira pas

emial au lieu de email. Quatre lettres transposées. Tu as relu ce code deux fois, ton collègue aussi : le cerveau humain corrige silencieusement ce qu'il lit. Il voit « email » même quand il y a « emial ». La machine, elle, compare lettre par lettre. Elle ne corrige pas. Elle compare.

En JavaScript pur, user.email vaut undefined et personne ne te le dit. En TypeScript, si tu déclares que user a la forme d'un Utilisateur, cette faute de frappe est refusée à la frappe, avec une suggestion : « Did you mean 'email'? »

Définition : une interface (ou un type alias) TypeScript est un contrat de forme : il décrit exactement quelles propriétés un objet doit avoir, leurs types, et lesquelles sont optionnelles ou en lecture seule. Tout objet qui prétend avoir cette forme est vérifié lettre par lettre par le compilateur.

Prédis avant de lire

L'objet a emial: au lieu de email:, et il est typé Utilisateur. À ton avis : que dit TypeScript, et que ferait JavaScript pur au même endroit ?

Voir la réponse

TypeScript refuse l'objet : propriété inconnue emial (il suggère email) ET propriété email manquante : deux erreurs pour une seule faute de frappe. JavaScript pur l'accepterait sans rien dire, et u.email vaudrait undefined en silence.

Décrire la forme exacte d'un objet

Une interface TypeScript, c'est un gabarit. Tu dis au compilateur : « tout objet de ce type DOIT avoir exactement ces propriétés, avec ces types ». Ni plus, ni moins :

interface Utilisateur {
  nom: string;
  email: string;
}

const u: Utilisateur = {
  nom: 'Sam',
  emial: 'sam@exemple.fr'   // ✗ 'emial' n'existe pas dans Utilisateur
                             //   Did you mean 'email'?
                             // ✗ 'email' est manquant
};

Le compilateur vérifie deux choses : chaque propriété déclarée dans l'interface est présente, et chaque propriété de l'objet existe dans l'interface. Une faute de frappe génère les deux erreurs à la fois.

Tu peux affiner le contrat avec deux modificateurs :

interface Utilisateur {
  readonly id: number;    // lecture seule après création : toute réaffectation est refusée
  nom: string;
  email: string;
  telephone?: string;     // optionnelle : présente → string, sinon absente
}

const u: Utilisateur = { id: 1, nom: 'Sam', email: 'sam@exemple.fr' };
u.id = 2;                 // ✗ Cannot assign to 'id' because it is a read-only property

telephone?: string signifie que la propriété peut être absente. Si elle est présente, TS force à gérer les deux cas avant d'utiliser la valeur : tu ne peux pas appeler u.telephone.length sans vérifier d'abord que u.telephone !== undefined.

À toi : attrape la faute de frappe

En JavaScript, cette typo vivrait des mois dans le code. Clique sur Vérifier les types et regarde le compilateur la pointer, et suggérer la correction. Clique d'abord sur Exécuter avant le fix : tu verras undefined s'afficher : le silence JS en action.

🧐 Labo TypeScript · le compilateur juge ton code (mode strict)
Bloqué sur la correction ? Voir le fix

Corrige emialemail. TS fait même la suggestion dans son message d'erreur. Une lettre changée, deux erreurs supprimées. Revérifie : ✓ 0 erreur, puis Exécute pour voir l'email s'afficher à la place d'undefined.

Erreur attendue : Object literal may only specify known properties, and 'emial' does not exist in type 'Utilisateur'. Did you mean 'email'? (+ propriété email manquante). C'est le compilateur qui joue le relecteur que le cerveau humain ne peut pas être.

Interface ou type : le choix honnête

TypeScript offre deux syntaxes pour décrire la forme d'un objet. Elles font (presque) la même chose :

// Syntaxe interface
interface Utilisateur {
  nom: string;
  email: string;
}

// Syntaxe type alias
type Utilisateur = {
  nom: string;
  email: string;
};

Résultat identique pour un objet. Mais type va plus loin : il peut nommer n'importe quoi (une union, un primitif, un tuple) :

type Id = string | number;           // union : impossible avec interface seule
type Statut = 'actif' | 'inactif';   // union de littéraux
type Paire = [string, number];       // tuple

Le piège de la fusion de déclarations : si tu déclares deux fois la même interface dans un projet, TypeScript les fusionne silencieusement (c'est la « declaration merging »). Pratique pour étendre des types de bibliothèques tierces, mais piège redoutable si c'est accidentel. type lève une erreur immédiate si tu le déclares deux fois : bien plus sûr au quotidien.

Reco pragmatique (Matt Pocock, Total TypeScript) : utilise type par défaut, il couvre tous les cas et ne fusionne pas silencieusement. Utilise interface quand tu veux délibérément l'héritage (extends) ou étendre des types de librairie via la fusion. En pratique : type pour tes modèles de données, interface pour les contrats d'API publiques que d'autres devront étendre.

The bug nobody catches in review

You have a sign-up form. The user submits, you send a confirmation email. Except the email never arrives. No error, no crash — just silence. Three days later you dig through the code and find this:

const user = {
  name: 'Sam',
  emial: 'sam@example.com'   // 👈 read carefully
};

sendConfirmation(user.email);   // undefined — email will never be sent

emial instead of email. Four transposed letters. You read this code twice, so did your colleague — the human brain silently corrects what it reads. It sees "email" even when there's "emial". The machine, on the other hand, compares character by character. It doesn't correct. It compares.

In plain JavaScript, user.email is undefined — and nobody tells you. In TypeScript, if you declare that user has the shape of a User, this typo is rejected at the keystroke, with a suggestion: "Did you mean 'email'?"

Definition: a TypeScript interface (or type alias) is a shape contract: it describes exactly which properties an object must have, their types, and which ones are optional or read-only. Every object claiming to have this shape is checked character by character by the compiler.

Predict before reading on

The object has emial: instead of email:, and it's typed as User. Your guess: what does TypeScript say, and what would plain JavaScript do in the same place?

Show the answer

TypeScript rejects the object: unknown property emial (it suggests email) AND missing property email — two errors for one typo. Plain JavaScript would accept it without a word, and u.email would be undefined in silence.

Describing the exact shape of an object

A TypeScript interface is a template. You tell the compiler: "any object of this type MUST have exactly these properties, with these types". No more, no less:

interface User {
  name: string;
  email: string;
}

const u: User = {
  name: 'Sam',
  emial: 'sam@example.com'   // ✗ 'emial' does not exist in User
                              //   Did you mean 'email'?
                              // ✗ 'email' is missing
};

The compiler checks two things: every property declared in the interface is present, and every property of the object exists in the interface. One typo generates both errors at once.

You can refine the contract with two modifiers:

interface User {
  readonly id: number;    // read-only after creation — any reassignment is rejected
  name: string;
  email: string;
  phone?: string;         // optional: present → string, otherwise absent
}

const u: User = { id: 1, name: 'Sam', email: 'sam@example.com' };
u.id = 2;                 // ✗ Cannot assign to 'id' because it is a read-only property

phone?: string means the property may be absent. If it's present, TS forces you to handle both cases before using the value — you can't call u.phone.length without first checking that u.phone !== undefined.

Your turn: catch the typo

In JavaScript, this typo would live in the code for months. Click Check the types and watch the compiler point it out — and suggest the fix. Click Run first, before the fix: you'll see undefined appear — JS silence in action.

🧐 TypeScript lab · the compiler judges your code (strict mode)
Stuck on the fix? Show it

Fix emialemail. TS even suggests it in the error message. One letter changed, two errors gone. Re-check: ✓ 0 errors, then Run to see the email appear instead of undefined.

Expected error: Object literal may only specify known properties, and 'emial' does not exist in type 'User'. Did you mean 'email'? (+ missing email property). That's the compiler playing the proofreader the human brain simply can't be.

Interface or type — the honest choice

TypeScript offers two syntaxes to describe the shape of an object. They do (almost) the same thing:

// interface syntax
interface User {
  name: string;
  email: string;
}

// type alias syntax
type User = {
  name: string;
  email: string;
};

Identical result for an object. But type goes further: it can name anything — a union, a primitive, a tuple:

type Id = string | number;             // union — not possible with interface alone
type Status = 'active' | 'inactive';   // literal union
type Pair = [string, number];          // tuple

The declaration merging trap: if you declare the same interface twice in a project, TypeScript silently merges them (this is "declaration merging"). Handy for extending third-party library types — a nasty trap when accidental. type raises an immediate error if declared twice: much safer day-to-day.

Pragmatic recommendation (Matt Pocock, Total TypeScript): use type by default — it covers every case and doesn't silently merge. Use interface when you deliberately want inheritance (extends) or to extend library types via merging. In practice: type for your data models, interface for public API contracts that others need to extend.

L'interface Utilisateur joue le rôle d'un gabarit : l'objet conforme (nom + email) passe sans problème (coche verte), l'objet avec 'emial' ne rentre pas dans le moule (croix rouge) et TypeScript suggère 'email'. interface Utilisateur nom: string email: string ← le gabarit { nom, email } nom: 'Sam' email: 'sam@exemple.fr' { nom, emial } emial: 'sam@exemple.fr' Did you mean 'email' ?
L'interface : un gabarit ; l'objet conforme passe, la faute de frappe est stoppée avec suggestion.
The User interface acts as a template: the conforming object (name + email) passes through (green check), the object with 'emial' doesn't fit the mould (red cross) and TypeScript suggests 'email'. interface User name: string email: string ← the template { name, email } name: 'Sam' email: 'sam@example.com' { name, emial } emial: 'sam@example.com' Did you mean 'email' ?
The interface: a template — the conforming object passes, the typo is stopped with a suggestion.

🎯 Pratique

S'entraîner (clique pour ouvrir) :

💬 Ré-explique sans regarder
Ré-explique sans regarder

Explique ce qu'apporte une interface face à un objet JS libre, avec l'exemple de la faute de frappe.

user.emial silently produces undefined; with an interface, the typo is caught at the keystroke, before anything runs.">Une bonne explication dit : l'interface est un contrat de forme : toute propriété manquante ou inconnue est refusée à la compilation avec une suggestion (« Did you mean… ? »). En JS libre, user.emial produit undefined en silence ; avec une interface, la faute de frappe est détectée au moment de la frappe, avant même d'exécuter.
🧠 Rappel libre
Rappel libre

Sans remonter : que font ? et readonly dans une interface ?

? rend la propriété optionnelle : si elle est présente, son type doit correspondre ; si elle est absente, TS force à vérifier avant tout usage (pas de undefined silencieux). readonly la rend en lecture seule après la création : toute réaffectation est refusée par le compilateur.
⚖️ Juge le code de l'IA
Accepter ou rejeter le code de l'IA

Ton objet ne passe pas la vérification de types : email est manquant. Tu demandes à l'IA de corriger. Elle répond : « Corrigé ! J'ai retiré email de l'interface Utilisateur, comme ça plus d'erreur. » Tu acceptes, ou tu rejettes ?

À rejeter. L'erreur disait la vérité : l'objet est incomplet, l'email manque vraiment. Retirer la propriété de l'interface pour faire taire le compilateur, c'est le mensonge inverse de as any (leçon 1) : on affaiblit le contrat pour qu'il ne dise plus rien, et l'email ne partira toujours pas. La bonne réponse : soit l'email est requis (on complète l'objet), soit il est vraiment optionnel (email?:) et alors on gère son absence partout dans le code.
À quoi sert une interface TypeScript ?
Que signifie telephone?: string dans une interface ?
Tu écris deux fois interface Config {…} dans le même projet. Que se passe-t-il ?
type ou interface : quelle est la reco pragmatique ?
Prochaine étape

Tu sais décrire la forme d'un objet. Mais parfois une valeur peut être deux choses différentes : un identifiant string ou number, un résultat ou une erreur. À la leçon 4, tu découvres les unions et l'art du narrowing : rétrécir l'incertitude jusqu'à savoir exactement ce que tu tiens.

Leçon 4 : Unions et narrowing →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement