Getting started General
Components
Forms
Trends
Utilities
Migrate from v1
  Join us
  JavaScript Articles

TypeScript function type : guide complet avec exemples

JavaScript

Tout sur le TypeScript function type avec des exemples clairs : declare function, method type, callbacks, génériques, surcharges et comment typer n'importe quelle fonction.

Syntaxe du TypeScript function type : paramètres, flèche et type de retour
TypeScript function type : guide complet avec exemples

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.

Commencez à créer avec Axentix

Prêt à créer des sites web exceptionnels ? Commencez avec le framework Axentix dès aujourd'hui.

Commencer

Articles similaires