Maîtriser les type guards et le type narrowing pour affiner les types et écrire du code TypeScript plus sûr.
Introduction aux Type Guards
Les Type Guards affinent le type d'une variable dans un bloc de code spécifique, permettant à TypeScript de déterminer le type exact dans chaque branche.
typeof Guard
Le guard typeof permet de différencier les types primitifs :
function process(value: string | number) {
if (typeof value === 'string') {
console.log(value.toUpperCase()); // TypeScript sait que c'est une string
} else {
console.log(value + 10); // TypeScript sait que c'est un number
}
}
instanceof Guard
Le guard instanceof permet de différencier les classes :
class Dog { bark() { console.log('Woof!'); } }
class Cat { meow() { console.log('Meow!'); } }
function pet(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark(); // Narrow à Dog
} else {
animal.meow(); // Narrow à Cat
}
}
Custom Type Guards
Un type guard personnalisé utilise la syntaxe value is Type comme retour :
interface User { id: number; name: string; }
interface Admin extends User { permissions: string[]; }
// La syntaxe "user is Admin" informe TypeScript du résultat
function isAdmin(user: User): user is Admin {
return 'permissions' in user;
}
const user: User | Admin = getUser();
if (isAdmin(user)) {
console.log(user.permissions); // TypeScript accepte — narrowed à Admin
}
Discriminated Unions
Une propriété discriminante commune permet de distinguer les membres d'une union :
type Success = { status: 'success'; data: any };
type Failure = { status: 'error'; message: string };
type Result = Success | Failure;
function handle(result: Result) {
if (result.status === 'success') {
console.log(result.data); // Narrowed à Success
} else {
console.log(result.message); // Narrowed à Failure
}
}
Type Narrowing
TypeScript affine automatiquement les types dans les blocs conditionnels :
function example(x: string | number) {
// Ici x est string | number
// console.log(x.toUpperCase()); // Erreur : number n'a pas toUpperCase
if (typeof x === 'string') {
console.log(x.toUpperCase()); // OK : narrowed à string
}
}
Patterns avancés
Le type never permet de vérifier l'exhaustivité d'un switch :
type Status = 'pending' | 'done' | 'error';
function handleStatus(status: Status): void {
if (status === 'pending') {
return;
} else if (status === 'done') {
return;
} else if (status === 'error') {
return;
}
// Si un cas est oublié, TypeScript signale une erreur ici
const _exhaustive: never = status;
}
Conclusion
Les Type Guards et le narrowing sont des outils essentiels pour travailler avec les types unions en TypeScript. Ils permettent à TypeScript de déterminer le type exact dans chaque branche de ton code.
Combine typeof, instanceof, les guards personnalisés et les discriminated unions pour écrire du code TypeScript robuste et sans cast forcé.
as). C'est plus sûr et plus explicite.