Types Avancés
Le niveau supérieur - unions discriminées, types mappés et conditionnels.
7.1Unions et types littéraux
TypeScripttypescript
// Union : un type OU un autre
type StringOuNumber = string | number
let id: StringOuNumber = "abc-123"
id = 42 // ✅ Aussi valide
// Types littéraux : des valeurs EXACTES comme types
type Direction = "haut" | "bas" | "gauche" | "droite"
let mouvement: Direction = "haut"
// mouvement = "diagonal" // ❌ Pas dans l'union
// Littéraux numériques
type DeFace = 1 | 2 | 3 | 4 | 5 | 6
let lancer: DeFace = 4
// lancer = 7 // ❌
// Préféré aux enums pour la légèreté
type Theme = "light" | "dark" | "system"
type Taille = "sm" | "md" | "lg" | "xl"
type HttpMethode = "GET" | "POST" | "PUT" | "DELETE"7.2Narrowing (rétrécissement de type)
Quand tu as un type union, TypeScript te demande de vérifier le type avant de l'utiliser :
TypeScripttypescript
function traiter(valeur: string | number) {
// typeof guard
if (typeof valeur === "string") {
// Ici, TypeScript sait que valeur est string
return valeur.toUpperCase()
}
// Ici, c'est forcément number
return valeur.toFixed(2)
}
// instanceof guard (pour les classes)
function formater(date: Date | string): string {
if (date instanceof Date) {
return date.toLocaleDateString("fr-FR")
}
return date // C'est un string
}
// "in" guard (pour les objets)
interface Chat { miauler(): void }
interface Chien { aboyer(): void }
function faireDuBruit(animal: Chat | Chien) {
if ("miauler" in animal) {
animal.miauler() // TypeScript sait que c'est un Chat
} else {
animal.aboyer() // TypeScript sait que c'est un Chien
}
}7.3Unions discriminées (Discriminated Unions)
Le pattern le plus puissant pour gérer des variants. Chaque type a une propriété commune qui sert de "badge d'identification" :
TypeScripttypescript
// Chaque variante a un "type" discriminant
interface ChargementEnCours {
statut: "chargement"
}
interface ChargementReussi<T> {
statut: "succes"
donnees: T
}
interface ChargementEchoue {
statut: "erreur"
message: string
}
type ChargementState<T> =
| ChargementEnCours
| ChargementReussi<T>
| ChargementEchoue
// TypeScript rétrécit le type grâce au discriminant
function afficher(state: ChargementState<string[]>) {
switch (state.statut) {
case "chargement":
return "Chargement..."
case "succes":
// TS sait que state.donnees existe ici
return state.donnees.join(", ")
case "erreur":
// TS sait que state.message existe ici
return `Erreur : ${state.message}`
}
}7.4Type Guards personnalisés
TypeScripttypescript
// Créer son propre garde de type
interface Poisson { nager(): void }
interface Oiseau { voler(): void }
// Le retour "animal is Poisson" est un type predicate
function estPoisson(animal: Poisson | Oiseau): animal is Poisson {
return (animal as Poisson).nager !== undefined
}
function deplacer(animal: Poisson | Oiseau) {
if (estPoisson(animal)) {
animal.nager() // ✅ TypeScript sait que c'est un Poisson
} else {
animal.voler() // ✅ TypeScript sait que c'est un Oiseau
}
}
// Garde utile au quotidien
function estDefini<T>(valeur: T | null | undefined): valeur is T {
return valeur !== null && valeur !== undefined
}
const valeurs = [1, null, 3, undefined, 5]
const definies = valeurs.filter(estDefini) // type: number[]7.5Types mappés
Transforme un type existant en un autre, propriété par propriété :
TypeScripttypescript
// Rendre toutes les propriétés optionnelles (comme Partial)
type MonPartial<T> = {
[K in keyof T]?: T[K]
}
// Rendre toutes les propriétés readonly
type MonReadonly<T> = {
readonly [K in keyof T]: T[K]
}
// Rendre toutes les propriétés nullable
type Nullable<T> = {
[K in keyof T]: T[K] | null
}
interface Formulaire {
nom: string
email: string
age: number
}
type FormulaireNullable = Nullable<Formulaire>
// { nom: string | null; email: string | null; age: number | null }
// Mapper les clés vers un nouveau type
type Drapeaux<T> = {
[K in keyof T]: boolean
}
type FormulaireValide = Drapeaux<Formulaire>
// { nom: boolean; email: boolean; age: boolean }7.6Types conditionnels
TypeScripttypescript
// Syntaxe : T extends U ? X : Y
type EstString<T> = T extends string ? "oui" : "non"
type Test1 = EstString<string> // "oui"
type Test2 = EstString<number> // "non"
// Extraire le type des éléments d'un tableau
type ElementDe<T> = T extends (infer E)[] ? E : never
type A = ElementDe<string[]> // string
type B = ElementDe<number[]> // number
type C = ElementDe<boolean> // never (pas un tableau)
// Exclure des types d'une union
type SansNull<T> = T extends null | undefined ? never : T
type Propre = SansNull<string | number | null | undefined>
// string | number
// C'est exactement ce que fait NonNullable<T> !
type Aussi = NonNullable<string | number | null> // string | number7.7Template Literal Types
TypeScripttypescript
// Types basés sur des chaînes de caractères
type Couleur = "rouge" | "bleu" | "vert"
type Taille = "petit" | "moyen" | "grand"
// Combinaison automatique !
type ClasseCSS = `${Taille}-${Couleur}`
// "petit-rouge" | "petit-bleu" | "petit-vert"
// | "moyen-rouge" | "moyen-bleu" | "moyen-vert"
// | "grand-rouge" | "grand-bleu" | "grand-vert"
// Exemple concret : événements
type Evenement = "click" | "scroll" | "keypress"
type HandlerName = `on${Capitalize<Evenement>}`
// "onClick" | "onScroll" | "onKeypress"
// Manipulation de chaînes au niveau des types
type Majuscule = Uppercase<"hello"> // "HELLO"
type Minuscule = Lowercase<"HELLO"> // "hello"
type Capitalise = Capitalize<"hello"> // "Hello"
type Decapitalise = Uncapitalize<"Hello"> // "hello"