Asserts Typescript Guard pour gérer des types d'éléments

author
Andréas Hanss · Jun 15, 2022
dev | 2 min
Image descriptive

Dans certains cas on défini un type TypeScript regroupant plusieurs types d'évènements et leur variables associées. Ou parfois c'est juste un type d'objet et la données associée.

Des types personnalisés

Par exemple on pourrait définir une liste de types de cartes à afficher sur une page d'accueil d'une application mobile.

On aurait par exemple :

  • Une carte de type Texte
  • Une carte de type Image
  • Une carte de type Vidéo

Chacune des carte ayant des données spécifiques à son type.

L'interface définissant cela pourrait être la suivante :

1
type TimelineCardsType =
2
| { type: "text"; data: { text: string; author: string } }
3
| { type: "image"; data: { URI: string; alt?: string; author: string } }
4
| {
5
type: "video";
6
data: {
7
URI: string;
8
format: "mp4" | "avi" | "mov";
9
durationMS: number;
10
alt?: string;
11
author: string;
12
};
13
};

À partir d'ici je souhaite throw une erreur si jamais l'évènement traité par mon code n'est pas celui que j'attends j'écrirais généralement le code. Concrètement dans un bloc de code cela pourrait se représenter de la sorte :

1
function renderTextCard(card: TimelineCardsType) {
2
if (card.type !== "text")
3
throw new Error("You must provide a card of type text.");
4
5
// Can be accessed
6
card.data.text;
7
}
8
9
function renderImageCard(card: TimelineCardsType) {
10
if (card.type !== "image")
11
throw new Error("You must provide a card of type image.");
12
13
// Can be jpg | png or svg
14
card.data.format;
15
}
16
17
function renderVideoCard(card: TimelineCardsType) {
18
if (card.type !== "image")
19
throw new Error("You must provide a card of type video.");
20
21
// Can be mp4 | avi or mov
22
card.data.format;
23
}

Problème, cela commence à devenir un petit peu verbeux et on se rend bien compte que l'on à tendance à répéter le même code avec le throw.

Comment faire pour extrait ce morceau de code et continuer de profiter de la sécurisation du type ?

La solution

1
function throwIfNotType<T extends TimelineCardsType["type"]>(
2
element: TimelineCardsType,
3
expectedType: T,
4
): asserts element is TimelineCardsType & { type: T } {
5
if (element.type !== expectedType)
6
throw Error(
7
`Unexpected type : got [${element.type}] expected [${expectedType}]`,
8
);
9
}

Cette fonction a maintenant un paramètre de type générique T qui est contraint par l'union des propriétés de type dans TimelineCardsType (Types Génériques).

La valeur de retour est asserts element is ce qui signifie qu'une exception doit être levée si l'élément (la carte) n'est pas du type indiqué. (Fonctions d'assertion).

TimelineCardsType & { type : T } signifie que l'union de tous les éléments est filtrée pour ne retenir que ceux qui correspondent au type passé en tant que expectedType. (Types d'intersection)

Désormais, cette fonction fonctionne sans problème et de cette manière on économise/factorise quelques lignes ;)