AccueilClientsExpertisesBlogOpen SourceContact

25 juillet 2023

Les branded types avec TypeScript

3 minutes de lecture

Les branded types avec TypeScript

Nous vous proposons dans cet article de découvrir un pattern avancé de TypeScript : les branded types. Nous allons voir comment les utiliser, et comment ils peuvent nous aider à améliorer la qualité de notre code.

Typage nominatif vs structurel

Afin de comprendre les branded types, il est important de comprendre la différence entre le typage nominatif et le typage structurel.

En TypeScript, le typage est dit structurel, c'est-à-dire que le compilateur va vérifier non pas le nom du type, mais sa structure. Ainsi TypeScript ne fait pas la différence entre ces deux types :

type Person = {
  name: string
  age: number
}

type Employee = {
  name: string
  age: number
}

Avec un language typé nominativement (comme PHP), ces deux types seraient différents car ils n'ont pas le même nom.

Le problème

Le fait que TypeScript soit un langage à typage structurel peut parfois poser problème. Prenons comme exemple ces deux types :

type AccountNumber = number
type PaymentAmount = number

Ils sont structurellement identiques, mais ne représentent pas (au sens métier) la même chose :

type AccountNumber = number
type PaymentAmount = number

function spend(accountNumber: AccountNumber, amount: PaymentAmount) {
  // ...
}

const amount: PaymentAmount = 100
const accountNumber: AccountNumber = 321321

// 💥 Pas d'erreur TS alors que l'on a inversé des données
spend(amount, accountNumber)

Ici on a interverti les deux variables lors de l'appel de spend(), problème : TypeScript ne lève pas d'erreur car les deux types sont bien structurellement identiques.

Il serait donc intéressant de pouvoir les différencier : c'est là qu'interviennent les branded types.

Les branded types

Grâce à une intersection, nous pouvons "tagguer" nos types afin de les différencier structurellement :

type AccountNumber = number & { __: 'AccountNumber' }
type PaymentAmount = number & { __: 'PaymentAmount' }

Nous pouvons ensuite créer des fonctions permettant de caster nos types grâce à un prédicat de type :

function isAccountNumber(accountNumber: number): accountNumber is AccountNumber {
  return accountNumber.toString().length === 13
}

Ainsi le/la développeur(euse) sera obligé de passer par cette fonction afin de créer un AccountNumber :

type AccountNumber = number & { _: 'AccountNumber' }
const accountNumber = 1263548749287

function logAccountNumber(accountNumber: AccountNumber) {
  console.log(accountNumber)
}

// TypeScript lève une erreur
logAccountNumber(accountNumber)

// TypeScript ne lève plus d'erreur car accountNumber est désormais de type AccountNumber
logAccountNumber(isAccountNumber(accountNumber))

Exemple concret

Prenons un autre exemple plus parlant. Nous voulons obliger une personne à passer par une fonction permettant de valider un email avant de l'utiliser. Nous créons donc un type ValidEmail :

type ValidEmail = string & { __: 'ValidEmail' }

Il est alors possible d'utiliser ce type pour s'assurer que la fonction isValidEmail() a bien été appelée avant :

const isValidEmail = (email: string): email is ValidEmail => {
  return email.includes('@')
}

const createUser = async (user: { email: ValidEmail }) => {
  return user
}

export const onSubmit = async (values: { email: string }) => {
  if (!isValidEmail(values.email)) {
    throw new Error('Email is invalid')
  }

  await createUser({
    email: values.email,
  })
}

Si l'on retire la vérification isValidEmail, TypeScript lèvera bien une erreur car la variable email n'aura pas été castée en ValidEmail :

export const onSubmit = async (values: { email: string }) => {
  // 💥 Erreur TypeScript
  await createUser({
    email: values.email,
  })
}

Conclusion

Les branded types (aussi connus sous le nom d'opaque, tagged, nominal types) permettent donc de simuler le typage nominatif en ajoutant "un tag" à la structure du type. Ce pattern permet de rajouter une couche de validation sur des parties de code sensible.

Si ce type de patterns vous intéresse, n'hésitez pas à participer à notre formation TypeScript !

À vos types ! 🛡

À découvrir également

Retour d'expérience chez 20 Minutes : s'adapter au journalisme numérique

30 May 2024

Retour d'expérience chez 20 Minutes : s'adapter au journalisme numérique

Dans cet article, vous ne trouverez pas de snippets ou de tips croustillants sur React, seulement le récit d'une aventure plutôt ordinaire à travers le développement et l'évolution d'un projet technique.

par

Vincent

Framer Motion : Animez vos applications en toute simplicité

01 Dec 2020

Framer Motion : Animez vos applications en toute simplicité

En matière de design web et mobile, le mouvement est aujourd’hui la norme...

par

Lucie

Améliorez vos composants avec Storybook

17 Nov 2020

Améliorez vos composants avec Storybook

Connaissez-vous Storybook ? Cet outil open-source offre un environnement...

par

Baptiste

Premier Octet vous accompagne dans le développement de vos projets avec typescript

En savoir plusNous contacter
18 avenue Parmentier
75011 Paris
+33 1 43 57 39 11
hello@premieroctet.com

Suivez nos aventures

GitHub
X (Twitter)
Flux RSS

Naviguez à vue