TypeScript 4.3 - Les alias de types natifs peuvent maintenant être utilisés comme clés

par

Si y'en a qui sont pas au courant, on peut enfin typer les clés d'un objet avec un alias d'une primitive. La feature est demandée depuis 2015 et n'est arrivée qu'il y a quelques jours :D
https://github.com/microsoft/TypeScript/issues/1778

En gros ça permet de faire ça :

Imaginons je veux typer un objet "Users" qui a comme clés un string qui correspond à un id utilisateur, et comme valeurs une entité User, je peux le faire comme ça :

type IdUser = string

interface User {
    id: IdUser
    firstName: string
    lastName: string
}

interface Users {
    [key: IdUser]: User
}

Alors qu'avant on devait le faire comme ça :
 

type IdUser = string

interface User {
    id: IdUser
    firstName: string
    lastName: string
}

interface Users {
    [key: string]: User
}


Après avoir parlé de cette nouveauté, j'ai eu pas mal de retours de personnes qui ne comprenaient pas à quoi serviraient des alias de types natifs. Voici quelques éléments de réponses ici :

- ça sert à avoir un code qui se documente tout seul, au lieu d'avoir un commentaire qui précise dans les cas de figure ou un string attendu en clé est un id de X chose, tu peux directement avoir un alias qui un type indiquant que c'est un id. C'est le genre de notion notamment importante dans la partie domaine en DDD ça permet d'avoir du code métier plus simple à lire.
 

- ça sert de pouvoir changer le type si besoin. Imaginons tu consommes une API qui te retourne un id sous forme d'int de 11 chiffres, dans ton code tu type partout en int, demain l'API t'annonce une mise à jour majeur et décide de changer leur façon de gérer les ids car ils ont atteint l'overflow, et ils ont donc choisi d'utiliser des uuid en string. ça veut dire que que si tu n'as pas d'alias, tu vas devoir chercher parmis tous les endroits où cet id est référencé en tant que number (donc toutes les signatures de fonction, les clés dans les objets, et j'en passe...), et le changer en tant que string (et j'espère pour toi qu'à ce moment là si t'as pas d'alias de type que t'as mis un commentaire qui d'indique que ton int est un idMachin). Alors que si t'as un alias de type, tu as juste à changer la valeur de ton alias, et c'est répercuté partout, le seul impact que tu auras à checker à ce moment-là, c'est si tu utilisais des methodes spécifiques aux number (très improbables dans l'exemple d'un id) qui ne s'appliqueraient pas à un string
 

- Autre exemple pour illustrer le 2ème point, si tu as une notion de monnaie dans ton application, par soucis de simplicité tu vas commencer par typer ta monnaie en tant que nombre. Donc tu vas typer number partout, et dans tous tes affichages tu vas faire appel à une fonction de formatting qui prendra un paramètre un nombre et retournera un string. Demain tu veux gérer plusieurs monnaies, comment tu fais ? J'imagine que t'auras envie de créer un objet qui représente ta monnaie, et qui aura un symbole, et là même chose que pour les ids, bonjour pour retrouver tous les number entre les fonctions d'affichage qui le prennent en paramètre, les fonction de calculs qui le prennent en paramètre.
Et là aussi j'espère que si t'as pas d'alias de type t'auras bien commenté tous tes number dans les divers methodes qui l'utilisent pour préciser que c'est une monnaie.
Alors que dès le départ, tu peux directement faire type Money = number, et le jour où tu voudras le changer pour un objet plus complexe, tu auras simplement à changer la façon de récupérer la valeur (car j'imagine t'auras un propriété value au lieu d'utiliser direct ta variable), et t'auras pas à changer les signatures de toutes tes fonctions qui utilisent une Money, car elles auront déjà le bon nom.

Sinon quelques cas de figure que j'ai eu aujourd'hui au taff', j'appelle une API qui digère des données à partir de l'API de DVF (je te laisse Google ce que c'est si t'es curieux) et m'en tire des infos dont j'ai besoin.

Et dans le retour j'ai un truc à peu près comme ça :

type ParcelleId = string
type MutationId = string

interface Local {
    // des trucs blabla...
    parcelle: ParcelleId 
}

interface Mutation {
    id: MutationId,
    locals: Local[]
    // des trucs blabla...
}

interface Parcelle {
    id: ParcelleId
    mutationIds: MutationId[]
    // des trucs blabla...
}

interface dvfJson {
    parcelles: Parcelle[]
    mutations: {[mutationId: MutationId]: Mutation}
}

 

Si j'enlève les alias de types pour mettre les types natifs à la place, Local.parcelle qui est string, je pourrais me poser la question si c'est un uuid, si c'est un nom, etc. Car l'API me donne pas un nom de clé idéale pour le déterminer rien qu'en lisant le nom.

Et j'évite au passage rien que dans mes définitions de types de répêter 3x et 2x respectivement les types de MutationId et ParcelleId