Menu

Surcharge de fonctions en TypeScript : les signatures d'overload

Les overloads de fonctions TypeScript permettent à une fonction d'avoir plusieurs signatures d'appel, chacune avec son propre type de retour. Découvrez le motif signatures de surcharge plus implémentation, les règles que vérifie le compilateur, quand un paramètre union est préférable, et les surcharges dans les classes.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

La surcharge de fonctions en TypeScript consiste à écrire plusieurs signatures d'appel pour une fonction, suivies d'une seule implémentation. Chaque signature peut associer des types de paramètres différents à un type de retour différent, et l'appelant obtient le type précis qui lui correspond.

Sans les surcharges, parse renverrait number | number[] pour chaque appel, et one + 1 serait une erreur tant que vous n'auriez pas affiné le résultat vous-même.

Signatures de surcharge et implémentation

Une fonction surchargée a deux parties :

  1. Les signatures de surcharge : des déclarations sans corps, une par forme d'appel prise en charge. Ce sont les seules signatures que les appelants peuvent utiliser.
  2. La signature d'implémentation : la dernière déclaration, avec le corps. Ses paramètres doivent accepter tout ce qu'acceptent les surcharges, et son type de retour doit couvrir celui de chaque surcharge. Elle est invisible de l'extérieur.

Les types n'existent qu'à la compilation : il n'y a donc qu'une seule fonction JavaScript à l'exécution. L'implémentation doit inspecter ses arguments (typeof, Array.isArray, arguments.length...) pour décider quoi faire. Le compilateur vérifie que les surcharges et l'implémentation concordent :

function format(value: string): string;
function format(value: number): number {
  return value;
}
// error TS2394: This overload signature is not compatible with its implementation signature.

La correction consiste à élargir l'implémentation : function format(value: string | number): string | number.

La signature d'implémentation n'est pas appelable

C'est la règle qui surprend le plus. Un appel doit correspondre à l'une des signatures de surcharge à elle seule ; TypeScript ne les combine pas.

Le compilateur affiche :

index.ts(12,19): error TS2769: No overload matches this call.
  The last overload gave the following error.
    Argument of type 'string | string[]' is not assignable to parameter of type 'string[]'.
      Type 'string' is not assignable to type 'string[]'.

L'implémentation accepte string | string[], mais les appelants ne la voient pas. Ajoutez une troisième surcharge qui prend l'union et renvoie l'union, et l'appel compile et affiche [ 1, 2 ] :

function parse(input: string): number;
function parse(input: string[]): number[];
function parse(input: string | string[]): number | number[];
function parse(input: string | string[]): number | number[] {
  return Array.isArray(input) ? input.map(Number) : Number(input);
}

Des nombres de paramètres différents

Les surcharges décrivent aussi des appels avec des nombres d'arguments différents. Ici, une date peut être construite à partir d'un timestamp ou d'une année, d'un mois et d'un jour, mais pas à partir de deux nombres :

Une seule signature avec deux paramètres optionnels accepterait makeDate(2024, 3) et construirait en silence une mauvaise date. Les surcharges en font une erreur de compilation (TS2575).

L'ordre compte

TypeScript essaie les surcharges de haut en bas et choisit la première qui correspond. Placez les signatures les plus précises en premier. Une surcharge large placée tôt dans la liste avale les appels destinés à celles qui la suivent :

function describe(value: unknown): string;   // matches everything
function describe(value: string): "text";    // never chosen
function describe(value: unknown): string {
  return typeof value === "string" ? "text" : "other";
}

const d = describe("hi"); // d: string, not "text"

Échangez les deux premières signatures et describe("hi") est typé "text".

Surcharges ou paramètre union ?

Les surcharges valent leurs lignes supplémentaires quand le type de retour dépend des types des arguments. Quand ce n'est pas le cas, une seule signature avec un paramètre union est plus courte, plus lisible, et accepte les arguments union que les surcharges refuseraient.

UtilisezQuand
Un paramètre unionLe type de retour est le même pour toutes les entrées
Des paramètres optionnelsLes formes d'appel ne diffèrent que par des derniers arguments qu'on peut librement omettre
Des surchargesLe type de retour change selon les arguments, ou certaines combinaisons d'arguments doivent être refusées
Un génériqueLe type de retour est construit à partir du type de l'argument, comme identity<T>(x: T): T

Un générique combiné à un type conditionnel peut exprimer certains ensembles de surcharges en une seule signature, mais pour deux ou trois cas, les surcharges sont en général plus lisibles.

Méthodes et constructeurs surchargés

Les méthodes utilisent le même motif dans une classe : des signatures de surcharge, puis la méthode avec son corps. Les constructeurs se surchargent de la même façon.

Les interfaces et les types objet peuvent aussi déclarer des surcharges, sous forme de plusieurs signatures d'appel ou de plusieurs signatures de méthode portant le même nom. Beaucoup de fonctions intégrées sont déclarées ainsi : survolez reduce sur un tableau dans un éditeur et il affiche « +2 overloads ».

Questions fréquentes

TypeScript permet-il la surcharge de fonctions ?

Oui, au niveau des types. Vous écrivez plusieurs signatures de surcharge (des déclarations sans corps) suivies d'une seule implémentation. Les appelants ne voient que les signatures de surcharge. Il n'y a toujours qu'une seule fonction JavaScript à l'exécution : l'implémentation vérifie donc elle-même les arguments et traite chaque cas.

Que signifie « No overload matches this call » ?

L'erreur TS2769 : les arguments ne correspondent à aucune signature de surcharge. La signature d'implémentation ne compte pas, donc un appel avec un argument union comme string | string[] échoue même quand l'implémentation l'accepte. Ajoutez une surcharge qui prend l'union, ou remplacez les surcharges par une seule signature.

Quand utiliser des surcharges plutôt qu'un type union ?

Utilisez des surcharges quand le type de retour dépend des types d'arguments passés, par exemple string en entrée donne number en sortie, mais string[] en entrée donne number[] en sortie. Quand le type de retour est le même pour toutes les entrées, une seule signature avec un paramètre union est plus simple et accepte aussi les arguments union.

Peut-on surcharger des fonctions fléchées en TypeScript ?

Pas avec la syntaxe de déclaration de surcharges, qui ne fonctionne que pour les déclarations function et les méthodes. Vous pouvez donner à une variable un type surchargé avec plusieurs signatures d'appel, type Parse = { (s: string): number; (s: string[]): number[] }, mais y assigner une fonction fléchée demande en général une assertion de type : une déclaration function est donc le choix le plus propre.

Pourquoi ma signature de surcharge n'est-elle pas compatible avec sa signature d'implémentation ?

L'erreur TS2394 signifie qu'une surcharge accepte ou renvoie quelque chose que l'implémentation n'accepte pas. Les paramètres de l'implémentation doivent accepter ceux de chaque surcharge, et son type de retour doit être compatible avec celui de chaque surcharge. Élargir l'implémentation (souvent en une union) corrige le problème.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER