Un type de fonction décrit les paramètres et la valeur de retour d'une fonction, et s'écrit avec une flèche : (a: number, b: number) => number. Nommez-le avec type et vous pourrez l'utiliser pour des variables, des paramètres et des propriétés d'objets.
La fonction assignée à add n'a besoin d'aucune annotation : les types de ses paramètres viennent d'Operation. C'est ce qu'on appelle le typage contextuel, et c'est la principale raison de nommer les types de fonctions.
Syntaxe des types de fonctions
La forme est (parameters) => ReturnType. Chaque paramètre demande un nom et un type. Les noms servent à la lisibilité et aux indications de l'éditeur ; toute fonction dont les types de paramètres sont compatibles correspond, quels que soient leurs noms.
type Predicate = (value: number) => boolean;
type Formatter = (value: number, digits?: number) => string; // optional parameter
type Logger = (...parts: string[]) => void; // rest parameter
type Factory = () => { id: number }; // no parameters
Le nom n'est pas facultatif. (string) => void déclare un paramètre appelé string sans type, et le mode strict le refuse avec TS7051: Parameter has a name but no type. Did you mean 'arg0: string'?. Écrivez (value: string) => void.
Typer les fonctions fléchées
Une fonction fléchée peut se typer de deux façons. Annotez la fonction elle-même, ou annotez la variable avec un type de fonction et laissez les paramètres être inférés.
La forme 1 est le choix habituel pour une fonction autonome. La forme 3 devient rentable quand plusieurs fonctions partagent une même signature (gestionnaires, comparateurs, convertisseurs), car la signature n'est écrite qu'une fois et toute modification est vérifiée partout.
Types de callbacks
Un paramètre dont le type est un type de fonction est un callback. La fonction de l'appelant est vérifiée par rapport à lui, et ses paramètres en sont inférés.
Deux règles rendent les callbacks agréables à utiliser :
- Moins de paramètres, c'est permis. Une fonction qui prend un paramètre peut être passée là où deux sont proposés. JavaScript ignore les arguments en trop, donc TypeScript l'autorise. Plus de paramètres que ce que propose le type est une erreur (
Target signature provides too few arguments.). - Un retour
voidaccepte tout. Un callback typé(...) => voidpeut renvoyer une valeur ; l'appelant promet de ne pas l'utiliser. C'est pourquoilist.forEach((x) => other.push(x))compile alors quepushrenvoie un nombre.
Les types des paramètres doivent tout de même être compatibles. Passer (x: string) => ... là où (a: number, b: number) => number est attendu échoue avec Types of parameters 'x' and 'a' are incompatible.
Signatures d'appel
(n: number) => string est un raccourci pour un type objet avec une signature d'appel : { (n: number): string }. La forme longue est nécessaire quand la fonction porte aussi des propriétés.
TypeScript vous permet d'ajouter des propriétés à une fonction déclarée avec const dans la même portée, et les suit dans le type de la fonction. Une interface peut aussi contenir une signature d'appel : interface Counter { (): number; count: number }.
Une signature de construction décrit quelque chose qu'on appelle avec new. Elle ajoute new devant : new (name: string) => User, ou { new (name: string): User } sous forme objet. C'est ainsi qu'on type un paramètre qui reçoit une classe.
Syntaxe méthode ou syntaxe propriété
Dans un type objet, un membre fonction peut s'écrire comme une méthode, handle(value: string): void, ou comme une propriété contenant une fonction, handle: (value: string) => void. Ils semblent interchangeables mais sont vérifiés différemment avec strict :
Le compilateur signale :
index.ts(8,27): error TS2322: Type '(value: string) => void' is not assignable to type '(value: string | number) => void'.
Types of parameters 'value' and 'value' are incompatible.
Type 'string | number' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.
L'erreur est justifiée : onlyStrings ne sait pas traiter un nombre. La forme méthode laisse passer la même erreur (les paramètres de méthode sont vérifiés de façon « bivariante » pour des raisons historiques), et a.handle(42) planterait à l'exécution avec TypeError: value.toUpperCase is not a function. Supprimez la ligne b pour voir ce plantage. Quand vous écrivez vos propres types objet, la forme propriété détecte davantage d'erreurs.
Évitez le type Function
Function est le type intégré auquel satisfait toute valeur de fonction. Ce n'est presque jamais ce que vous voulez : TypeScript ne connaît ni les paramètres ni le type de retour, donc chaque appel est accepté et renvoie any.
Remplacez Function par la vraie signature. Quand vous acceptez vraiment n'importe quelle fonction (un debounce générique, un wrapper de journalisation), (...args: never[]) => unknown accepte toutes les fonctions tout en gardant le résultat unknown au lieu de any. Pour les fonctions dont les types dépendent de leur entrée, voir génériques : un type de fonction générique ressemble à <T>(value: T) => T.
Questions fréquentes
Comment définir un type de fonction en TypeScript ?
Utilisez la syntaxe fléchée : (a: number, b: number) => number. Donnez-lui un nom avec un alias de type, type Compare = (a: number, b: number) => number;, et utilisez ce nom pour les variables, les paramètres et les propriétés. Les noms des paramètres font partie de la syntaxe mais ne servent qu'à la documentation : une fonction dont les paramètres portent d'autres noms correspond quand même.
Comment typer un paramètre callback en TypeScript ?
Écrivez le type de fonction comme type du paramètre : function onEach(items: string[], cb: (item: string, index: number) => void) { ... }. Les appelants peuvent passer une fonction qui prend moins de paramètres, et les paramètres du callback sont inférés à partir de ce type : onEach(list, (item) => ...) n'a donc besoin d'aucune annotation.
Pourquoi éviter le type Function en TypeScript ?
Function accepte n'importe quelle fonction et son appel n'est pas vérifié : tous les arguments sont autorisés et le résultat est any. Écrivez plutôt la vraie signature, comme () => void ou (value: string) => number, ou (...args: never[]) => unknown quand vous acceptez vraiment toutes les fonctions.
Quelle est la différence entre un type de fonction et une signature d'appel ?
Ils décrivent la même chose. (n: number) => string est un raccourci pour le type objet { (n: number): string }. La forme signature d'appel est nécessaire quand la fonction possède aussi des propriétés, par exemple { (n: number): string; label: string }.
Comment typer une fonction fléchée en TypeScript ?
Soit vous annotez directement ses paramètres et son type de retour, const half = (n: number): number => n / 2;, soit vous donnez un type de fonction à la variable et laissez les paramètres être inférés : const half: (n: number) => number = (n) => n / 2;.