Un TypeScript function type (type de fonction) décrit les paramètres qu'une fonction accepte et la valeur qu'elle retourne. Il s'écrit avec une liste de paramètres, une flèche, puis le type de retour :
type Sum = (a: number, b: number) => number
const sum: Sum = (a, b) => a + b
Cette ligne couvre la plupart des besoins du quotidien, mais c'est aussi sur les fonctions que TypeScript cache le
plus de surprises : les méthodes ne sont pas vérifiées comme les propriétés de type fonction, void ne veut
pas dire ce que vous croyez, et le type natif Function est un piège. Ce guide passe en revue toutes les
façons de typer une fonction en TypeScript, dans l'ordre où vous en aurez besoin. Chaque extrait et chaque
message d'erreur ci-dessous a été vérifié avec le compilateur TypeScript en mode strict.
Qu'est-ce qu'un function type en TypeScript ?
En JavaScript, les fonctions sont des valeurs. Vous pouvez les stocker dans des variables, les passer en argument et les retourner depuis d'autres fonctions. Puisque ce sont des valeurs, il leur faut un type, et ce type est la signature de la fonction : le type de chaque paramètre plus le type de retour.
Prenons cette fonction JavaScript :
function sum(a, b) {
return a + b
}
Elle prend deux nombres et retourne un nombre. L'expression de type de fonction correspondante est :
(a: number, b: number) => number
Cela ressemble à une fonction fléchée, mais c'est un type : la partie à droite de => est le
type de retour, pas le corps de la fonction. Donnez-lui un nom avec un alias de type et vous pouvez le
réutiliser partout où un type est attendu :
type Sum = (a: number, b: number) => number
// Comme type de variable
const add: Sum = (a, b) => a + b
// Comme type de paramètre (un callback)
function calculate(operation: Sum): number {
return operation(4, 3)
}
// Comme type de retour
function createSum(): Sum {
return (a, b) => a + b
}
calculate(add) // 7
createSum()(1, 2) // 3
Remarquez que a et b n'ont aucune annotation dans les implémentations. Dès qu'une fonction est
assignée à quelque chose qui a un type de fonction, TypeScript déduit le type des paramètres à partir de ce type.
C'est le typage contextuel, et c'est ce qui rend les callbacks typés si agréables à écrire.
Le nom des paramètres est obligatoire dans le type
L'erreur la plus fréquente quand on écrit un type de fonction pour la première fois est d'oublier le nom du paramètre :
type Log = (string) => void
// error TS7051: Parameter has a name but no type. Did you mean 'arg0: string'?
TypeScript lit (string) => void comme "un paramètre nommé string sans type". Sans le mode
strict, cela compile sans rien dire et le paramètre devient any, ce qui est pire. Écrivez toujours
(message: string) => void. Le nom lui-même ne sert que de documentation : l'implémentation est libre
d'appeler le paramètre autrement.
Comment déclarer une fonction en TypeScript
Il existe trois façons d'écrire une fonction, et toutes produisent le même type de fonction. Seul l'emplacement des annotations change.
// 1. Déclaration de fonction : paramètres et type de retour annotés sur place
function sum(a: number, b: number): number {
return a + b
}
// 2. Expression de fonction
const sum2 = function (a: number, b: number): number {
return a + b
}
// 3. Fonction fléchée
const sum3 = (a: number, b: number): number => a + b
// Les trois ont le type (a: number, b: number) => number
L'annotation du type de retour est facultative, car TypeScript le déduit des instructions return. L'écrire
quand même est une bonne habitude pour les fonctions exportées : l'erreur apparaît dans la fonction qui retourne
la mauvaise valeur, et non dans chaque fichier qui l'appelle.
Une déclaration de fonction ne peut pas être annotée directement avec un alias de type, puisqu'il n'y a
pas de variable à laquelle rattacher le type. Pour vérifier une fonction par rapport à un type existant, utilisez
satisfies sur une expression ou assignez-la simplement :
type Sum = (a: number, b: number) => number
const sum = ((a, b) => a + b) satisfies Sum
function plainSum(a: number, b: number) {
return a + b
}
const checked: Sum = plainSum // OK, les signatures correspondent
Les déclarations de fonctions sont aussi hoistées, contrairement aux fonctions fléchées stockées dans un
const. Si cette différence est floue, notre guide du hoisting JavaScript l'explique en détail.
Le mot-clé declare function
declare function est autre chose. Il indique au compilateur qu'une fonction existe à l'exécution, sans en
fournir le corps. Rien n'est émis dans le JavaScript généré. On l'utilise pour les fonctions qui viennent d'un
endroit que TypeScript ne voit pas : une balise script, une globale injectée par votre bundler ou une librairie
non typée.
// globals.d.ts
declare function gtag(command: 'config', id: string): void
declare function gtag(
command: 'event',
name: string,
params?: Record<string, unknown>
): void
// n'importe où dans votre application
gtag('event', 'signup') // OK, entièrement typé
Deux règles à retenir : un declare function n'a jamais de corps, et plusieurs déclarations du même nom
forment des surcharges. Dans un fichier .d.ts, les fonctions et variables de premier niveau doivent
commencer par declare ou export.
Typer les paramètres d'une fonction
Paramètres optionnels et valeurs par défaut
Ajoutez ? après le nom pour rendre un paramètre optionnel. Son type devient number | undefined dans
la fonction, il faut donc gérer le cas où il est absent.
type Sum = (a: number, b?: number) => number
const sum: Sum = (a, b = 0) => a + b
sum(2) // 2
sum(2, 3) // 5
Les valeurs par défaut appartiennent uniquement à l'implémentation. Un type de fonction ne peut pas contenir
b = 0 : marquez le paramètre comme optionnel dans le type et définissez la valeur par défaut dans la
fonction, comme ci-dessus. Les paramètres optionnels doivent venir après tous les paramètres obligatoires.
Paramètres rest
Un paramètre rest se type avec un type tableau. Utilisez un tuple quand le nombre et la position des arguments sont fixes.
type SumAll = (...numbers: number[]) => number
const sumAll: SumAll = (...numbers) => numbers.reduce((total, n) => total + n, 0)
sumAll(1, 2, 3) // 6
// Mixte : un paramètre obligatoire, puis autant de chaînes que voulu
type Log = (level: 'info' | 'error', ...messages: string[]) => void
// Tuple : exactement 2 ou 3 arguments, avec des libellés
type ToPoint = (...coords: [x: number, y: number, z?: number]) => string
Paramètres déstructurés
L'annotation se place après le motif de déstructuration complet, jamais à l'intérieur. Écrire
{ url: string } dans le motif renommerait url en une variable appelée string.
type RequestOptions = { url: string; retries?: number }
function request({ url, retries = 3 }: RequestOptions): Promise<Response> {
return fetch(url)
}
Typer la valeur de retour
| Type de retour | À utiliser quand | Exemple |
|---|---|---|
void |
La fonction ne retourne rien d'utile | (message: string) => void |
Promise<T> |
La fonction est async ou retourne une promesse |
(id: string) => Promise<User> |
never |
La fonction lève toujours une erreur ou ne se termine jamais | (message: string) => never |
value is T |
La fonction est un type guard | (value: unknown) => value is string |
// Les fonctions async retournent toujours une Promise
type SumAsync = (a: number, b: number) => Promise<number>
const sumAsync: SumAsync = async (a, b) => a + b // OK
const wrong: SumAsync = (a, b) => a + b
// error TS2322: Type 'number' is not assignable to type 'Promise<number>'.
// never : le code après un appel à fail() est inatteignable
function fail(message: string): never {
throw new Error(message)
}
// Type guard : affine le type de l'argument chez l'appelant
const isString = (value: unknown): value is string => typeof value === 'string'
TypeScript method type : typer une méthode
Une méthode est une fonction qui vit sur un objet. Son type s'écrit dans un type objet ou une interface, et il existe deux syntaxes :
interface Calculator {
// Syntaxe méthode : deux-points avant le type de retour
sum(a: number, b: number): number
// Syntaxe propriété : une propriété qui contient un type de fonction
product: (a: number, b: number) => number
// Méthode optionnelle
reset?(): void
}
const calculator: Calculator = {
sum(a, b) {
return a + b
},
product: (a, b) => a * b
}
Les deux décrivent quelque chose que l'on appelle avec calculator.sum(1, 2), et la même syntaxe fonctionne
dans un alias type. Dans une classe, les méthodes s'annotent exactement comme des déclarations de
fonctions :
class Counter {
count = 0
increment(): void {
this.count++
}
// Propriété fonction fléchée : `this` est toujours l'instance
decrement = (): void => {
this.count--
}
}
Syntaxe méthode ou syntaxe propriété : la vérification n'est pas la même
La plupart des tutoriels présentent les deux syntaxes comme interchangeables. Elles ne le sont pas. Avec
strictFunctionTypes (inclus dans strict), les paramètres des propriétés de type fonction
sont vérifiés strictement, alors que ceux des méthodes sont vérifiés de façon permissive (bivariance)
pour des raisons de rétrocompatibilité. Cette vérification permissive laisse passer du code dangereux :
interface Animal { name: string }
interface Dog extends Animal { bark(): void }
interface WithMethod {
handle(animal: Animal): void
}
interface WithProperty {
handle: (animal: Animal) => void
}
const dogHandler = (dog: Dog) => dog.bark()
const a: WithMethod = { handle: dogHandler } // OK ?! Plantera avec un chat
const b: WithProperty = { handle: dogHandler }
// error TS2322: Type '(dog: Dog) => void' is not assignable to type '(animal: Animal) => void'.
// Types of parameters 'dog' and 'animal' are incompatible.
// Property 'bark' is missing in type 'Animal' but required in type 'Dog'.
WithMethod accepte un handler qui ne fonctionne que pour les chiens alors qu'il promet de gérer n'importe
quel animal. WithProperty détecte le bug. Pour les callbacks et les gestionnaires d'événements stockés sur
des objets, préférez la syntaxe propriété. Gardez la syntaxe méthode pour les vraies méthodes qui s'appuient sur
this.
Call signatures : les interfaces de fonction
Une expression de type de fonction possède une forme longue appelée call signature. C'est un type objet avec une liste de paramètres et pas de nom :
interface Sum {
(a: number, b: number): number
}
// Strictement équivalent à
type Sum2 = (a: number, b: number) => number
Pour une fonction simple, la forme fléchée est plus courte et plus lisible. La call signature devient nécessaire dans deux cas : quand la fonction porte aussi des propriétés, et quand elle a plusieurs surcharges (voir plus bas).
interface SumWithDescription {
(a: number, b: number): number
description: string
}
// Les propriétés doivent exister au moment de l'assignation
const sum: SumWithDescription = Object.assign(
(a: number, b: number) => a + b,
{ description: 'Adds two numbers' }
)
sum(1, 2) // 3
sum.description // 'Adds two numbers'
Une erreur fréquente consiste à assigner d'abord la fonction seule, puis à ajouter la propriété sur la ligne
suivante. Cela échoue avec
Property 'description' is missing in type '(a: number, b: number) => number', car la valeur est incomplète
au moment où elle est assignée. Object.assign construit la valeur complète en une seule expression.
Construct signatures
Placez new devant une signature pour décrire quelque chose qui doit être appelé avec new, le plus
souvent une classe passée en argument :
type Constructor<T> = new (...args: any[]) => T
function create<T>(Ctor: Constructor<T>): T {
return new Ctor()
}
class Logger {}
const logger = create(Logger) // Logger
Comment typer un callback qui accepte n'importe quelle fonction
Tôt ou tard, vous écrivez un utilitaire qui prend "une fonction quelconque" sans se soucier de sa signature
exacte : un debounce, un memoize, un bus d'événements. Le but est d'accepter n'importe quelle fonction mais de
refuser tout le reste, sans retomber sur any. Trois candidats s'offrent à vous.
| Type | Accepte | L'appeler retourne | Verdict |
|---|---|---|---|
Function |
Toute fonction, et les classes | any, arguments non vérifiés |
À éviter |
(...args: any[]) => any |
Toute fonction, classes refusées | any, arguments non vérifiés |
Très bien comme contrainte générique |
(...args: never[]) => unknown |
Toute fonction, classes refusées | unknown, et impossible de l'appeler à la légère |
Le plus sûr si vous ne faites que la stocker ou la transmettre |
type AnyFunction = (...args: any[]) => any
function runLoose(callback: Function) {
return callback(1, 2, 3) // aucune vérification, retourne any
}
function run(callback: AnyFunction) {
return callback()
}
class Logger {}
runLoose(Logger) // Compile, puis lève : Class constructor Logger cannot be invoked without 'new'
run(Logger)
// error TS2345: Argument of type 'typeof Logger' is not assignable to parameter of type 'AnyFunction'.
Function est le type de Function.prototype : il correspond à tout ce qui est appelable, y compris
les classes, qui lèvent une erreur quand on les appelle sans new. La règle typescript-eslint
no-unsafe-function-type le signale pour cette raison.
En pratique, on veut rarement "n'importe quelle fonction" comme type final. On veut conserver la
signature fournie par l'appelant. C'est le rôle d'un générique contraint par AnyFunction :
function debounce<T extends (...args: any[]) => void>(
fn: T,
delay: number
): (...args: Parameters<T>) => void {
let timer: ReturnType<typeof setTimeout>
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => fn(...args), delay)
}
}
const log = debounce((message: string) => console.log(message), 200)
log('saved') // OK
log(42)
// error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
Le any ne fuit jamais : il n'apparaît que dans la contrainte, et la fonction retournée a exactement les
paramètres de l'originale.
Types de fonction génériques
Les génériques relient les types des paramètres entre eux ou au type de retour. Avec les types de fonction, la position du paramètre de type compte, et elle détermine qui choisit le type.
// Fonction générique : l'appelant choisit T à chaque appel
type Identity = <T>(value: T) => T
const identity: Identity = (value) => value
identity('a') // string
identity(1) // number
// Alias de type générique : T et U sont fixés quand le type est utilisé
type Mapper<T, U> = (value: T) => U
const toLength: Mapper<string, number> = (value) => value.length
toLength('hello') // 5
Utilisez la première forme pour les fonctions qui marchent avec n'importe quel type (identity,
map, first). Utilisez la seconde pour décrire une famille de types de fonction apparentés, comme
Mapper<User, string> et Mapper<Order, number>.
Surcharges de fonction (overloads)
Quand une fonction peut être appelée de plusieurs façons distinctes, listez d'abord les signatures, puis écrivez une seule implémentation qui les gère toutes. La signature d'implémentation n'est pas appelable de l'extérieur.
function makeDate(timestamp: number): Date
function makeDate(month: number, day: number, year: number): Date
function makeDate(monthOrTimestamp: number, day?: number, year?: number): Date {
return day !== undefined && year !== undefined
? new Date(year, monthOrTimestamp, day)
: new Date(monthOrTimestamp)
}
makeDate(1700000000000) // OK
makeDate(5, 12, 2026) // OK
makeDate(5, 12)
// error TS2575: No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
Pour exprimer des surcharges sous forme de type de fonction, écrivez plusieurs call signatures dans un même type objet :
type Convert = {
(input: string): number
(input: number): string
}
Ne dégainez pas les surcharges trop vite. Si les signatures ne diffèrent que par le type d'un argument et
retournent la même chose, une union comme (input: string | number) => string est plus simple et
fonctionne avec davantage d'appelants.
Typer this dans une fonction
TypeScript permet de déclarer le type de this sous la forme d'un faux premier paramètre. Il est effacé du
JavaScript généré et ne compte pas comme un argument.
interface User {
name: string
admin: boolean
}
type UserFilter = (this: User) => boolean
const isAdmin: UserFilter = function () {
return this.admin
}
isAdmin.call({ name: 'Ada', admin: true }) // OK
isAdmin()
// error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type 'User'.
Cela ne fonctionne qu'avec les expressions function. Les fonctions fléchées prennent le this de la
portée englobante, elles ne peuvent donc pas avoir de paramètre this. Pour le comportement à l'exécution,
consultez notre guide sur la méthode bind et le binding de this en JavaScript.
Les règles d'assignabilité qui surprennent
Moins de paramètres, c'est permis ; plus, non
type Sum = (a: number, b: number) => number
const first: Sum = (a) => a // OK
const zero: Sum = () => 0 // OK
const three: Sum = (a: number, b: number, c: number) => a + b + c
// error TS2322: Type '(a: number, b: number, c: number) => number' is not assignable to type 'Sum'.
// Target signature provides too few arguments. Expected 3 or more, but got 2.
Ignorer un argument est toujours sans danger en JavaScript, et c'est ce que vous faites chaque fois que vous
écrivez items.map((item) => ...) sans utiliser l'index. Exiger un argument que l'appelant ne passera
jamais n'est pas sûr, donc c'est refusé.
Un callback void peut retourner une valeur
type Callback = () => void
const callback: Callback = () => 42 // OK
const result = callback() // result est void, pas number
function strict(): void {
return 42
}
// error TS2322: Type 'number' is not assignable to type 'void'.
Dans un type de fonction, void signifie "la valeur de retour sera ignorée", et non "ne doit rien
retourner". C'est ce qui rend items.forEach((item) => list.push(item)) valide alors que push
retourne un nombre. Sur une déclaration de fonction, void est strict et retourner une valeur est une
erreur.
Ne rendez pas optionnels les paramètres d'un callback
function each(items: string[], callback: (item: string, index?: number) => void) {
items.forEach((item, index) => callback(item, index))
}
each(['a', 'b'], (item, index) => console.log(index.toFixed()))
// error TS18048: 'index' is possibly 'undefined'.
Marquer index comme optionnel revient à dire "je vous appellerai peut-être sans lui", ce qui oblige chaque
appelant à gérer undefined. Comme les callbacks peuvent déjà ignorer les arguments en trop, déclarez le
paramètre comme obligatoire : (item: string, index: number) => void.
Extraire les types d'une fonction existante
Vous n'avez pas toujours besoin d'écrire un type de fonction à la main. TypeScript fournit des types utilitaires qui le lisent depuis une fonction existante, ce qui garde les deux synchronisés.
function createUser(name: string, age: number) {
return { id: crypto.randomUUID(), name, age }
}
type CreateUser = typeof createUser
// (name: string, age: number) => { id: string; name: string; age: number }
type CreateUserParams = Parameters<typeof createUser>
// [name: string, age: number]
type User = ReturnType<typeof createUser>
// { id: string; name: string; age: number }
async function fetchUser(id: string) {
return { id, name: 'Ada' }
}
type FetchedUser = Awaited<ReturnType<typeof fetchUser>>
// { id: string; name: string }
Aide-mémoire du TypeScript function type
| Besoin | Syntaxe |
|---|---|
| Type de fonction | (a: number, b: number) => number |
| Paramètre optionnel | (a: number, b?: number) => number |
| Paramètres rest | (...values: number[]) => number |
| Pas de valeur de retour | (message: string) => void |
| Fonction async | (id: string) => Promise<User> |
| Méthode dans une interface | sum(a: number, b: number): number |
| Fonction avec propriétés | { (a: number): number; description: string } |
| Constructeur | new (...args: any[]) => T |
| N'importe quelle fonction | (...args: any[]) => any |
| Fonction générique | <T>(value: T) => T |
this typé |
(this: User) => boolean |
| Type guard | (value: unknown) => value is string |
| Déclaration ambiante | declare function name(arg: string): void |
Foire aux questions
Faut-il utiliser type ou interface pour un type de fonction ?
Utilisez un alias type avec la syntaxe fléchée. C'est plus court et c'est ce qu'utilisent la plupart des
projets ainsi que le handbook TypeScript. Une interface avec une call signature produit le même type et ne
vaut le coup que si la fonction a aussi des propriétés ou si vous avez besoin de la fusion de déclarations.
Quelle est la différence entre un function type et un method type ?
Un function type existe seul et utilise => avant le type de retour. Un method type se déclare dans un type
objet et utilise : avant le type de retour. Au-delà de la syntaxe, les paramètres des méthodes sont
vérifiés moins strictement que ceux des propriétés de type fonction avec strictFunctionTypes.
Peut-on utiliser le type Function en TypeScript ?
Vous pouvez, mais vous ne devriez pas. Function accepte les classes, ne vérifie pas les arguments et
retourne any. Écrivez la vraie signature, ou (...args: any[]) => any quand la fonction peut
réellement être n'importe quoi.
Quelle est la différence entre void et undefined comme type de retour ?
() => undefined oblige la fonction à retourner undefined et laisse l'appelant utiliser cette
valeur. () => void accepte une fonction qui retourne n'importe quoi et indique à l'appelant de ne pas
utiliser le résultat. Pour les callbacks, void est presque toujours le bon choix.
Est-ce que declare function génère du JavaScript ?
Non. declare function est effacé à la compilation. Il décrit seulement une fonction qui existe déjà à
l'exécution. Si rien ne fournit cette fonction, le code compile puis échoue avec une ReferenceError.
Conclusion
Le TypeScript function type, c'est une liste de paramètres, une flèche et un type de retour :
(a: number, b: number) => number. Nommez-le avec un alias type et laissez le typage contextuel
remplir les paramètres de vos implémentations.
À partir de là, chaque variante répond à un besoin précis : un method type dans un objet, une call signature
quand la fonction a des propriétés ou des surcharges, un générique quand les types de l'appelant doivent être
conservés, et declare function quand l'implémentation vit en dehors de TypeScript. Gardez trois règles en
tête et la plupart des erreurs de type de fonction s'expliquent d'elles-mêmes : une implémentation peut prendre
moins de paramètres mais jamais plus, void dans un type de fonction signifie "ignoré", et les propriétés
de type fonction sont vérifiées plus strictement que les méthodes.
Pour la référence complète, le chapitre sur les fonctions du handbook TypeScript est l'étape suivante. Et si vous modélisez des ensembles de valeurs fixes à côté de vos fonctions, jetez un oeil à notre guide des enums en JavaScript.