Kodokon kodokon.com

Narrowing : typeof, in, instanceof et type guards

Vous maîtrisez les mécanismes de rétrécissement de type et écrivez vos propres prédicats pour fiabiliser les unions.

10 min · 3 questions

Ouvrir cette leçon dans Kodokon

Le narrowing est le mécanisme par lequel TypeScript affine un type large (une union, unknown) vers un type plus précis en analysant le flux de contrôle. Vous l'utilisez déjà sans y penser : chaque if sur une valeur typée déclenche une analyse. Le compilateur suit trois familles de gardes natifs : typeof pour les primitives, instanceof pour les instances de classes, et in pour la présence d'une propriété. Bien les choisir évite les casts sauvages avec as, qui désactivent la vérification au lieu de la guider.

TYPESCRIPT
function formatValue(
  value: string | number | Date
): string {
  if (typeof value === "string") {
    return value.trim();
  }
  if (value instanceof Date) {
    return value.toISOString();
  }
  return value.toFixed(2);
}
Chaque branche affine le type ; la dernière ne peut être que number.

L'opérateur in est précieux quand vous manipulez des objets structurellement différents sans champ discriminant. Attention toutefois : in teste la présence d'une clé, y compris héritée, pas son type. Sur des données externes (API, JSON), il rétrécit selon vos déclarations, pas selon la réalité à l'exécution. Le piège classique du métier : un typeof value === "object" laisse passer null, car typeof null vaut "object" en JavaScript. Ajoutez toujours un test de nullité explicite.

TYPESCRIPT
interface Cat { meow(): void }
interface Dog { bark(): void }

function speak(animal: Cat | Dog): void {
  if ("meow" in animal) {
    animal.meow();
  } else {
    animal.bark();
  }
}
Narrowing par présence de propriété avec l'opérateur in.

Quand la logique de garde devient réutilisable ou trop complexe pour l'analyse de flux, écrivez un type guard personnalisé : une fonction dont le retour est annoté value is T. Le compilateur fait alors confiance à votre implémentation. C'est un contrat : si votre test est incomplet, vous réintroduisez des erreurs à l'exécution que le typage ne verra jamais. Depuis TypeScript 5.5, les prédicats simples sont inférés automatiquement dans les callbacks comme filter, mais l'annotation explicite reste la norme pour les fonctions exportées.

TYPESCRIPT
interface User { id: string; email: string }

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    typeof value.id === "string" &&
    "email" in value &&
    typeof value.email === "string"
  );
}

const raw: unknown =
  JSON.parse('{"id":"1","email":"a@b.c"}');
if (isUser(raw)) {
  console.log(raw.email.toLowerCase());
}
Un prédicat qui valide une donnée externe avant usage.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Pourquoi le test typeof value === "object" est-il insuffisant pour rétrécir vers un objet non nul ?
    • Parce que typeof renvoie "Object" avec une majuscule
    • Parce que typeof null vaut aussi "object" en JavaScript
    • Parce que typeof ne fonctionne pas sur les unions
    • Parce que les tableaux renvoient "array"
  2. Quelle annotation de retour transforme une fonction en type guard personnalisé ?
    • boolean
    • value as User
    • value is User
    • asserts value
  3. Quel est le principal risque d'un type guard personnalisé mal écrit ?
    • Une erreur de compilation dans la fonction
    • Un narrowing mensonger : le compilateur croit un type faux à l'exécution
    • Une perte de performance à la compilation