Définissez vos types à partir d'autres déjà existants
TypeScript propose des types de base pour gérer des nombres, des chaînes de caractères et des booléens. Pour les cas les plus simples, utiliser ces types est suffisant. Cela dit, il peut être intéressant de créer ses propres types de variables pour plus de clarté dans le code : un type qui se nomme Age
ou Username
sera plus parlant que number
ou string
.
Pour créer un type, il suffit d’utiliser le mot-clé type
, suivi du nom que l’on veut lui donner. On place ensuite un =
et on lui indique le type TypeScript qu’il doit avoir :
type MyType = AnotherType;
Comme vous le voyez, cela ressemble beaucoup à la définition d’une variable JavaScript ! Si cela vous aide à mieux comprendre, vous pouvez voir les types comme étant les variables de TypeScript.
OK, très bien, mais ton exemple n’est pas très parlant… C’est quoi ce AnotherType
?
Il peut s’agir de n’importe quel type TypeScript ! string
, number
, boolean
, et même d’autres types que nous avons nous-mêmes créés. Voici ci-dessous quelques exemples :
type Age = number;
type Firstname = string;
type Lastname = Firstname;
Je vais vous faire un aveu : nous ne créons pas réellement de nouveaux types en procédant ainsi. Nous définissons en fait ce qu’on appelle des alias.
Un alias est juste un renommage d’un type déjà existant. Si deux types sont l’alias d’un même autre type, alors ces deux alias seront identiques aux yeux de TypeScript !
Je sens que je vous ai perdu (j’ai dû moi-même me relire plusieurs fois en écrivant cette phrase 😅), un exemple sera sans doute plus parlant :
// On définit deux alias Money et Age, pour qu’ils soient identiques à 'number'
type Money = number;
type Age = number;
// On définit une fonction qui s'attend à recevoir un Age (et donc un nombre)
function checkAge(ageToCheck: Age) {/*...*/}
// On appelle la fonction en lui donnant de l'argent (et donc aussi un nombre !)
const lotOfMoney: Money = 500000;
checkAge(lotOfMoney); // Money étant un nombre comme Age, TS ne voit pas d'erreur
Mais alors, quel est l’intérêt des alias ?
De par leurs noms plus explicites que number
ou string
, les alias permettent de rendre le code plus clair en servant de documentation.
Un autre intérêt est qu’un alias peut faire référence à plusieurs types :
type CodeSecret = string | number;
Le code qui suit est par conséquent valide :
type CodeSecret = string | number;
const code1: CodeSecret = 123;
const code2: CodeSecret = '4@cK3r';
Enfin, petite astuce utile : une valeur JavaScript peut servir de type TypeScript valide. Un tel type n’autorise alors que cette valeur :
type Ten = 10;
const five: Ten = 5; // Erreur TS : Type '5' is not assignable to type '10'
const ten: Ten = 10; // OK !
Là, comme ça, vous devez encore vous demander l’intérêt de cette astuce… Et pourtant, c’est très pratique ! En effet, combinée avec le principe d’union qu’on vient de voir, elle nous permet de gérer une liste de valeurs autorisées :
type Creature = 'blob' | 'troll' | 'unicorn' | 'dragon';
function fightCreature(target: Creature) {}
fightCreature('dragon'); // OK !
fightCreature('fish'); // Erreur TS
Cette astuce pourra également vous servir plus tard, dans des notions plus avancées. Mais chaque chose en son temps. 😉
Créez un objet complexe
Comme d'habitude, voyons d'abord comment faire en vidéo, et ensuite, on récapitule :
Au chapitre précédent, nous avons vu qu’il était possible de typer un objet comme paramètre d’une fonction :
function damage(characterToDamage: { life: number }, amount: number): number
Il est tout à fait possible d’utiliser le mot-clé type
de la même manière. Ici, l’exemple pourrait s’écrire ainsi :
type Hero = {
life: number;
};
function damage(characterToDamage: Hero, amount: number): number
Il est bien entendu possible d’ajouter autant de propriétés que l’on veut à notre objet, et elles-mêmes peuvent avoir n’importe quels types :
type Pet = {
name: string;
life: number;
attack: number;
defense: number;
};
type Hero = {
name: string;
life: number;
attack: number;
defense: number;
pet?: Pet;
};
Notez le ?
sur la dernière propriété de Hero
: elle indique que pet
est optionnelle. Un objet Hero
sera ainsi valide, qu’il ait ou non un animal de compagnie.
Cet exemple est également intéressant car il est très redondant : on répète beaucoup de choses ici. Un Hero
a en effet les mêmes propriétés qu’un Pet
(animal de compagnie, en français) : il a juste cette propriété pet
en plus ! (On est d’accord qu’un animal de compagnie ne peut pas avoir lui-même d’animal de compagnie, n’est-ce pas ?)
Pour rendre ce code moins répétitif, nous pouvons créer un type intermédiaire contenant toutes les propriétés en commun, que nous appellerons dans cet exemple Character
:
type Character = {
name: string;
life: number;
attack: number;
defense: number;
};
type Pet = Character;
type Hero = Character & {
pet?: Pet;
};
Pet
est donc ici un alias de Character
.
Le symbole &
indique que le type Hero
est une intersection entre le type Character
et { pet?: Pet }
.
Une intersection indique à TypeScript que l’on souhaite fusionner les types entre eux. En clair, ici on dit à TypeScript que Hero
est un Character
avec une propriété optionnelle pet
, qui a pour type Pet
. Et ça tombe bien, c’était ce qu’on voulait faire !
Petit bonus : type ou interface ?
En lisant du code TypeScript d’autres développeurs, vous pouvez tomber sur un autre mot-clé très similaire à type
: le mot-clé interface
.
Il semble donc plus correct d’utiliser interface
pour définir nos objets. Voici d’ailleurs l’exemple précédent écrit avec ce mot-clé :
interface Character {
name: string;
life: number;
attack: number;
defense: number;
};
type Pet = Character;
interface Hero extends Character {
pet?: Pet;
};
Comme vous le voyez, les différences de syntaxe sont assez minimes. Alors qu’on utilisait le caractère &
pour faire une intersection entre deux types
, on utilise le mot-clé extends
pour qu’une interface
hérite d’une autre : les résultats dans les deux cas sont équivalents !
Notez par ailleurs que cet exemple utilise toujours le mot-clé type
pour définir Pet
: on veut en effet que ce type soit un alias de Character
, donc son utilisation reste ici pertinente.
Historiquement, type
et interface
servaient deux finalités différentes, mais au fur et à mesure des évolutions de TypeScript, ces différences sont devenues de plus en plus mineures, utiles seulement pour des cas plus avancés. À notre niveau, nous n’exploiterons pas ces différences subtiles entre type
et interface
, vous pouvez donc utiliser le mot-clé que vous préférez !
Manipulez des listes fortement typées
Dans un programme, il est très fréquent de devoir manipuler des listes de variables, souvent appelées des tableaux. En JavaScript, un même tableau peut contenir différents types de variables : des nombres, des booléens, des objets… n’importe quoi !
Avec TypeScript, il est possible de dire à notre code qu’un tableau (ou Array
) contiendra des données avec un type spécifique. Pour ce faire, il suffit d’ajouter deux crochets []
à droite du type souhaité :
type MyArrayOfNumbers = number[]; // Définition d'un tableau de nombres
// Que des nombres, c'est OK !
const arrayOk: MyArrayOfNumbers = [1, 2, 3];
// Autre chose que des nombres : erreur TypeScript !
const arrayNotOk: MyArrayOfNumbers = [1, 'two', false];
Comme le montre cet exemple, lorsque vous manipulez un tableau typé, vous vous assurez qu’il ne contiendra que des variables du type que vous avez défini : en effet, si votre code va contre cette règle, TypeScript vous alerte !
Notez que si vous ne trouvez pas cette syntaxe avec []
assez claire, il en existe une autre un peu plus explicite, avec le mot-clé Array
. L’exemple qui suit est ainsi l’exact équivalent de ce qu’on vient de voir : type MyArrayOfNumbers = Array<number>;
.
Là encore, c’est juste un choix personnel : vous pouvez utiliser la syntaxe que vous préférez !
À vous de jouer !
Le but de cet exercice est de corriger des types déjà définis et d’en créer d’autres pour que TypeScript ne remonte plus d’erreurs. Arriverez-vous à typer des animaux magiques et des pièces d’équipement pour nos héros ?
En résumé
Le mot-clé
type
sert à définir des alias.Le caractère
?
indique qu’une propriété est optionnelle.Il est possible d’utiliser des valeurs JavaScript en tant que types TypeScript.
Il existe deux syntaxes pour définir un tableau :
Type[]
ouArray<Type>
.
Avec ce que nous venons de voir, vous pouvez déjà typer beaucoup de concepts pour vos projets. Mais TypeScript peut aller encore plus loin : dans le prochain chapitre, nous verrons comment définir des types génériques, c’est-à-dire des types ne représentant pas la même chose selon la façon dont on les appelle !