Le journal · Développement et ingénierie
Asserts Typescript Guard pour gérer des types d'éléments
Utiliser des gardiens de types personnalisés permet de factoriser son code tout en s'assurant de la cohérence des types.
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 :
type TimelineCardsType =
| { type: "text"; data: { text: string; author: string } }
| { type: "image"; data: { URI: string; alt?: string; author: string } }
| {
type: "video";
data: {
URI: string;
format: "mp4" | "avi" | "mov";
durationMS: number;
alt?: string;
author: string;
};
};À 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 :
function renderTextCard(card: TimelineCardsType) {
if (card.type !== "text")
throw new Error("You must provide a card of type text.");
// Can be accessed
card.data.text;
}
function renderImageCard(card: TimelineCardsType) {
if (card.type !== "image")
throw new Error("You must provide a card of type image.");
// Can be jpg | png or svg
card.data.format;
}
function renderVideoCard(card: TimelineCardsType) {
if (card.type !== "image")
throw new Error("You must provide a card of type video.");
// Can be mp4 | avi or mov
card.data.format;
}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
function throwIfNotType<T extends TimelineCardsType["type"]>(
element: TimelineCardsType,
expectedType: T,
): asserts element is TimelineCardsType & { type: T } {
if (element.type !== expectedType)
throw Error(
`Unexpected type : got [${element.type}] expected [${expectedType}]`,
);
}function renderTextCard(card: TimelineCardsType) {
if (card.type !== "text")
throw new Error("You must provide a card of type text.");
// Can be accessed
card.data.text;
}
function renderImageCard(card: TimelineCardsType) {
if (card.type !== "image")
throw new Error("You must provide a card of type image.");
// Can be jpg | png or svg
card.data.format;
}
function renderVideoCard(card: TimelineCardsType) {
if (card.type !== "image")
throw new Error("You must provide a card of type video.");
// Can be mp4 | avi or mov
card.data.format;
}function renderTextCard(card: TimelineCardsType) {
throwIfNotType(card, "text");
// Can be accessed
card.data.text;
}
function renderImageCard(card: TimelineCardsType) {
throwIfNotType(card, "image");
// Can be jpg | png or svg
card.data.format;
}
function renderVideoCard(card: TimelineCardsType) {
throwIfNotType(card, "video");
// Can be mp4 | avi or mov
card.data.format;
}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 ;)