Classes TypeScript, unknown et never : les types qui manquaient
Modificateurs d'accès, unknown vs any, never et le débat enums.
Objectifs
À la fin de cette leçon, vous saurez :
- écrire une classe TypeScript avec propriétés typées et constructeur raccourci ;
- choisir entre
public,privateetprotecteden connaissance de cause ; - expliquer pourquoi
unknownest l'alternative sûre àany; - utiliser
neverpour rendre unswitchexhaustif ; - argumenter pour ou contre les
enumface aux unions littérales.
Une classe TypeScript, c'est une classe JS plus un contrat
interface Tache {
id: number;
titre: string;
}
class Gestionnaire implements Tache {
private historique: string[] = [];
constructor(
public readonly id: number,
public titre: string,
private limite = 50,
) {}
renommer(nouveauTitre: string): void {
this.titre = nouveauTitre;
this.historique.push(`renommée en "${nouveauTitre}"`);
}
}
Trois choses à lire dans ce constructeur : les paramètres déclarés avec un modificateur (public, private…) créent et initialisent la propriété correspondante en une seule ligne — c'est le raccourci canonique de NestJS. implements vérifie que la classe respecte l'interface. readonly interdit la réaffectation après construction.
public, private, protected : trois niveaux d'intimité
| Modificateur | Accessible depuis la classe ? | Depuis une sous-classe ? | Depuis l'extérieur ? |
|---|---|---|---|
public (défaut) | oui | oui | oui |
protected | oui | oui | non |
private | oui | non | non |
class Utilisateur {
protected motDePasseHash: string; // les enfants peuvent lire
private sel: string; // personne d'autre
constructor(mph: string, sel: string) {
this.motDePasseHash = mph;
this.sel = sel;
}
}
class Admin extends Utilisateur {
verifier() {
console.log(this.motDePasseHash.length); // ok : protected
// console.log(this.sel); // erreur : private
}
}
Point capital : ces modificateurs sont vérifiés à la compilation seulement. Dans le JavaScript généré, tout est public — contrairement aux champs # du chapitre 28, réellement privés à l'exécution. En TypeScript, on les utilise comme un contrat documenté que le compilateur fait respecter.
unknown : le any honnête
Le type any désactive le typage : tout passe, aucune aide, erreurs garanties en production. unknown décrit la même réalité (« valeur de type inconnue ») mais exige de vérifier avant d'utiliser :
function traiter(entree: unknown) {
// entree.truc; // erreur : pas encore prouvé
if (typeof entree === "string") {
console.log(entree.toUpperCase()); // ok : narrowing vers string
}
}
C'est exactement le réflexe du chapitre 19 (valider TOUT ce qui entre), exprimé dans le système de types : données d'API, JSON parsé, paramètres d'URL → unknown, puis garde de type.
never : le type vide au service de l'exhaustivité
never est le type des valeurs qui n'existent jamais : une fonction qui plante ou boucle à l'infini « ne retourne jamais ». Son usage le plus utile : verrouiller un switch.
type Forme = { genre: "cercle"; rayon: number } | { genre: "carre"; cote: number };
function aire(f: Forme): number {
switch (f.genre) {
case "cercle": return Math.PI * f.rayon ** 2;
case "carre": return f.cote ** 2;
default: {
const impossible: never = f; // erreur si un cas manque !
throw new Error("cas inconnu");
}
}
}
Ajoutez demain { genre: "triangle" } : la ligne const impossible: never = f refuse de compiler tant que le triangle n'est pas géré. Le compilateur devient votre checklist.
Les enum face aux unions littérales
// Version enum
enum Statut { Ouvert = "OPEN", Ferme = "CLOSED" }
// Version union littérale (préférée ici)
type Statut2 = "OPEN" | "CLOSED";
Les deux garantissent qu'aucune valeur sauvage ne passe. Pourquoi préférer l'union ? Elle reste du JavaScript pur après compilation (de simples chaînes), se sérialise sans surprise en base comme en API, et s'intègre au narrowing. L'enum ajoute une structure à l'exécution et des pièges numériques connus. Retenez les deux pour lire le code existant ; choisissez l'union par défaut.
Exercice
- Réécrivez cette classe avec constructeur raccourci, puis supprimez une ligne grâce au raccourci :
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
- Créez
CompteBancaireavecsoldeenprivateet une méthodeprotectedjournaliser(message); vérifiez qu'unCompteEpargne extends CompteBancairepeut journaliser mais pas lire… puis tentez d'accéder àsoldedepuis l'extérieur : quelle erreur ? - Écrivez
function premier(valeur: unknown): stringqui rend"texte"pour une string,"nombre"pour un number,"autre"sinon. - Ajoutez
{ genre: "triangle"; base: number; hauteur: number }au typeForme: constatez l'erreur de compilation surnever, puis corrigez. - Expliquez pourquoi
anyaurait masqué les trois exercices précédents.
Résumé
constructor(public x: number)crée et initialise la propriété : idiome central de NestJS.public/protected/privatesont des contrats vérifiés à la compilation, pas des murs d'exécution.unknownremplaceanypartout où une donnée externe entre : obligé de vérifier avant d'agir.never+defaulttransforme un cas oublié en erreur de compilation.- Préférer les unions littérales aux
enumpour leur simplicité d'exécution.
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Réponses détaillées
Question 1.
class Point {
constructor(public x: number, public y: number) {}
}
Deux lignes de moins, même JavaScript généré (propriétés assignées dans le constructeur).
Question 2. CompteEpargne.journaliser(...) fonctionne (protected descend aux sous-classes). Depuis l'extérieur : Property 'solde' is private and only accessible within class 'CompteBancaire'. À l'exécution, la propriété existe bel et bien — seul le compilateur protège.
Question 3.
function premier(valeur: unknown): string {
if (typeof valeur === "string") return "texte";
if (typeof valeur === "number") return "nombre";
return "autre";
}
Question 4. Le default produit : Type '{ genre: "triangle"; ... }' is not assignable to type 'never'. On ajoute case "triangle": return (f.base * f.hauteur) / 2; et l'erreur disparaît — preuve que le filet fonctionne.
Question 5. Avec entree: any, chaque accès compile sans garde : premier accepterait n'importe quoi sans jamais forcer les typeof, la classe accepterait n'importe quel objet, et le switch manquant resterait silencieux jusqu'à la production.