Imaginez que vous avez un besoin de différencier deux types qu'on appellera Roman
et Arabic
pour représenter deux notations de nombres.
Dans Roman
, une méthode text()
nous donnera du texte :
class Roman {
text(): string {
return 'I';
}
}
Dans Arabic
, une méthode number()
nous donnera un nombre :
class Arabic {
number(): number {
return 2;
}
}
Pour assembler ces deux types, je vous propose d'utiliser les Union Types :
type Numeral = Roman | Arabic;
Maintenant, on a soit
Roman
soitArabic
pour le typeNumeral
, c'est exhaustif.
Ça permet de faire des choses comme :
const stringify = (numeral: Numeral): string => {
if (numeral instanceof Roman) {
return numeral.text(); // Ça ne peut être qu'un Roman
}
return numeral.number().toString(); // Il ne reste plus que Arabic vu qu'on a déjà traité le Roman
};
Ici
stringify
donne le résultat des méthodes spécifiques àRoman
etArabic
sous forme de texte.
Parfois, on se dit que ça serait bien d'extraire le instanceof
dans une méthode :
abstract class NumeralIs {
isRoman(): boolean {
return this instanceof Roman;
}
}
class Roman extends NumeralIs {/* … */}
class Arabic extends NumeralIs {/* … */}
Sauf que quand on fait ça, on perd l'inférence de type :
const stringify = (numeral: Numeral): string => {
if (numeral.isRoman()) {
return numeral.text(); // C'est cassé parce que le type est toujours Numeric
}
return numeral.number().toString(); // C'est cassé pour la même raison
};
Pour que tout fonctionne à nouveau, il existe le mot clé is
en TypeScript qui permet d'assurer le type de retour suite à une opération booléenne :
abstract class NumeralIs {
isRoman(): this is Roman { /* ... */ } // On affirme que si c'est vrai, ça sera un Roman
}
Maintenant l'inférence fonctionne à nouveau :
const stringify = (numeral: Numeral): string => {
if (numeral.isRoman()) {
return numeral.text(); // Roman est bien retrouvé
}
return numeral.number().toString(); // Roman a été traité, ça ne peut plus être que Arabic
};
Pour résumer, on vient de voir les concepts théoriques suivants :
- Les Union Types qu'on appelle aussi "type somme" et qui font partie de la famille des types algébriques ;
- L'inférence de type qui découvre le type en fonction de son contexte d'exécution ;
- L'extraction de méthode, une des techniques de refactoring.
Et aussi, le is
de TypeScript qui est peu connu.
N'hésitez pas à pratiquer le Type-Driven Development pour poursuivre vos connaissances sur les types et leurs usages.