Leçon 2 / 20

Définir les types du domaine en TypeScript strict

15 min React — architecture, performance et accessibilité Gratuit

Objectif : Modéliser les dépenses et les catégories avec des types qui empêchent des états invalides.

Une dépense enregistrée ne devrait pas pouvoir contenir un montant textuel ou une catégorie inconnue sans que le problème soit visible dans votre code. À l’inverse, une saisie en cours peut être incomplète : lui donner le même type qu’une dépense enregistrée rend les cas valides difficiles à exprimer. Vous allez définir des types métier qui distinguent ces données et font apparaître certaines erreurs dès la vérification TypeScript.

La leçon précédente a réparti les responsabilités entre la page et ses composants. Ici, vous préparez les contrats de données qui permettront à ces zones de manipuler les mêmes concepts sans confondre une dépense, une catégorie et une saisie encore incomplète.

Définir les données métier

Dans src/types/depense.ts, vous pouvez nommer les identifiants et les objets qui décrivent le domaine. Un alias comme IdentifiantDepense améliore la lisibilité et facilite les changements ultérieurs, même si, à lui seul, il reste représenté par une chaîne de caractères. Le type Depense décrit une dépense déjà enregistrée ; NouvelleDepense décrit les champs nécessaires avant de lui attribuer un identifiant.

src/types/depense.tsTypeScript
export type IdentifiantDepense = string;
export type IdentifiantCategorie = string;

export interface Categorie {
  id: IdentifiantCategorie;
  nom: string;
}

export interface Depense {
  id: IdentifiantDepense;
  description: string;
  montant: number;
  categorieId: IdentifiantCategorie;
  date: string;
}

export type NouvelleDepense = Omit<Depense, "id">;

export type SelectionCategorie =
  | { type: "toutes" }
  | { type: "categorie"; categorieId: IdentifiantCategorie };

Une catégorie est identifiée par id, tandis que son nom sert à l’affichage. La dépense conserve donc categorieId plutôt qu’une copie du nom : cela permet de relier la donnée métier à la catégorie correspondante. La date est représentée ici par une chaîne, format que les champs de date du projet peuvent fournir ; le type string ne garantit toutefois pas, à lui seul, que cette chaîne soit une date valide.

Exprimer des cas distincts sans champs ambigus

SelectionCategorie est une union discriminée : chaque variante possède une propriété type dont la valeur permet à TypeScript de reconnaître la forme de l’objet. Le cas « toutes » ne transporte pas d’identifiant inutile ; le cas « catégorie » en exige un. Cette structure rend le choix explicite au lieu d’utiliser une chaîne spéciale telle que "toutes" à l’endroit où le programme attend un identifiant.

Exemple d’utilisation des typesTypeScript
import type {
  Depense,
  SelectionCategorie,
} from "./depense";

const depense: Depense = {
  id: "depense-001",
  description: "Abonnement de transport",
  montant: 42.5,
  categorieId: "transport",
  date: "2025-03-12",
};

const selection: SelectionCategorie = {
  type: "categorie",
  categorieId: depense.categorieId,
};

function decrireSelection(valeur: SelectionCategorie): string {
  if (valeur.type === "toutes") {
    return "Toutes les catégories";
  }

  return `Catégorie sélectionnée : ${valeur.categorieId}`;
}

console.log(decrireSelection(selection));
Résultat attendu
Catégorie sélectionnée : transport

Après le test de valeur.type, TypeScript sait que la branche restante contient un categorieId. Si vous essayez de construire la variante { type: "categorie" } sans cet identifiant, le compilateur signale une erreur. De même, affecter une chaîne à montant est refusé : ce champ est déclaré comme un nombre.

Relier les types au tableau de bord

Le modèle distingue les dépenses enregistrées des nouvelles dépenses sans imposer dès maintenant une organisation de leur stockage. Dans l’interface, une catégorie possède un identifiant stable et un nom lisible ; un tableau peut afficher le nom associé à categorieId, alors que la dépense conserve son identifiant. Les prochaines leçons décideront où ces données résident et comment elles sont fournies aux composants.

Respectez aussi la différence entre absence et valeur par défaut. Si une dépense n’a pas encore été sélectionnée, le code qui la représente doit le dire explicitement, par exemple avec Depense | null ; n’utilisez pas un objet incomplet rempli de chaînes vides pour contourner le contrôle du compilateur. La nullabilité stricte vous oblige alors à traiter le cas absent avant d’accéder aux propriétés de la dépense.

À retenir

  • Décrivez séparément une dépense enregistrée, une nouvelle dépense et une catégorie.
  • Utilisez un identifiant de catégorie dans la dépense, plutôt que d’y recopier son nom d’affichage.
  • Choisissez une union discriminée quand plusieurs formes de données valides doivent être distinguées.
  • Avec la nullabilité stricte, indiquez explicitement les valeurs qui peuvent être absentes et traitez ce cas.
  • Les types détectent des incohérences à la compilation ; ils ne valident pas à eux seuls les données reçues à l’exécution.

Dans la prochaine leçon, vous attribuerez à chaque donnée du tableau de bord un emplacement cohérent et éviterez de conserver deux copies d’une même information.

Envie d'aller plus loin ?

Les cours Enovatic sont gratuits et le resteront. Si vous cherchez un accompagnement encadré, nos formations professionnelles sont animées par les mêmes formateurs.