ProgrammationDéveloppeur Fullstack

Comment fonctionne la compatibilité structurelle des types (Structural typing) dans TypeScript ? En quoi cela diffère-t-il de la typage nominal, quelle est sa force et quels pièges peuvent se présenter ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question

Contrairement à de nombreux langages orientés objets, TypeScript met en œuvre une typage structurel (duck typing) : un objet est considéré comme correspondant à un type s'il a toutes les propriétés nécessaires, indépendamment de son (non) déclaration explicite de ce type.

Problème

Cette flexibilité peut parfois conduire à des acceptations inattendues d'objets pour un type, si la structure correspond, même s'ils ne sont en fait pas liés en termes de sens. Cela est dangereux dans des modèles de données complexes où la correspondance de la structure est accidentelle.

Solution

Structurez toujours correctement les types d'objets, minimisez les correspondances de structure entre différentes entités, pour les cas critiques, utilisez des propriétés ou des symboles supplémentaires pour la « nominalisation » des types.

Exemple de code :

interface Point { x: number; y: number; } interface Pixel { x: number; y: number; } function drawPoint(p: Point) { console.log(p.x, p.y); } const pixel: Pixel = { x: 1, y: 2 }; drawPoint(pixel); // OK, les types sont structurellement compatibles

Caractéristiques clés :

  • Vérification par structure (propriétés et leurs types), et non par nom de type
  • Facilité d'intégration avec le code JS existant
  • Conflits possibles lors de la correspondance de structures d'objets de sens différent

Questions pièges.

Si la structure de deux interfaces coïncide, cela signifie-t-il qu'elles sont entièrement interchangeables ?

Peut-être sur le plan de la structure, mais pas sur le plan de la logique du programme. Cela est acceptable au niveau du compilateur, mais peut entraîner des erreurs logiques (par exemple, Point et Pixel ci-dessus).

Peut-on interdire la compatibilité structurelle pour un certain type ?

Pas complètement, mais on peut ajouter une propriété unique (par exemple, avec un symbole) :

interface Brand { _brand: unique symbol; }

Désormais, un autre objet ne pourra pas imiter ce type sans le même symbole unique.

En quoi la typage structurelle diffère-t-elle de la typage nominale ?

La structure est basée sur la présence de la structure, la nominale sur la correspondance du nom du type dans un espace de noms donné. Dans TS, le typage est toujours structurel par défaut.

Erreurs typiques et anti-modèles

  • Acceptation erronée d'objets non liés avec une structure identique
  • Correspondance complète des signatures — impossibilité de séparer conceptuellement différents types
  • Faux sentiment de sécurité des types

Exemple de la vie réelle

Cas négatif

Dans plusieurs entités, les champs se sont coïncidés involontairement (par exemple, User et Admin comme {id: number, name: string}), ce qui a conduit à de la confusion lors du travail avec des contrats API.

Avantages :

  • Moins de code, plus facile d'étendre de nouvelles entités

Inconvénients :

  • Difficile de détecter des erreurs logiques que le compilateur ne capture pas

Cas positif

Utilisation de « marqueurs » uniques symboliques et de champs non standard pour délimiter des types sémantiquement différents avec une structure identique.

Avantages :

  • Séparation explicite par logique commerciale
  • Sécurité et clarté supplémentaires des concepts

Inconvénients :

  • Complexité supplémentaire du code
  • Nécessite une planification plus soignée des types