tezvyn:

TypeScript Function Types: Expressions vs. Signatures

AI-drafted, machine-checkedSource: typescriptlang.orgintermediate

TypeScript describes function shapes with type expressions or call signatures. Use a simple expression like `(s: string) => void` for callbacks. For functions with properties, use a call signature. The footgun: parameter names are required in type expressions.

WHY IT EXISTS In JavaScript and TypeScript, functions are first-class citizens—they are values that can be passed to other functions, returned from them, and assigned to variables. To maintain type safety, TypeScript needs a way to describe the "shape" of these function values: what arguments they expect and what they return.

THE MENTAL MODEL Think of a function type as a contract for a callable value. It defines the function's signature, including its parameters and return type. TypeScript offers different syntaxes for this contract depending on whether the function is a simple callback, a callable object with properties, or a class constructor.

HOW IT WORKS TypeScript provides three main ways to describe a function's type.

First, the Function Type Expression: (a: string) => void. This syntax looks like an arrow function and is the simplest way to type a function. It's ideal for callbacks and variables holding a function. The parameter name (a in this case) is required. Writing (string) => void is a common mistake; it defines a function with a parameter named string of type any.

Second, the Call Signature: { (someArg: number): boolean; description: string; }. This is written inside an object type. It's for describing a value that is callable but also has its own properties. Note the use of a colon : instead of an arrow => to separate the parameters from the return type.

Third, the Construct Signature: { new (s: string): SomeObject; }. By adding the new keyword, you describe a function that can be used as a constructor (i.e., called with new). This can be combined with a call signature for objects like JavaScript's Date that can be invoked with or without new.

WHEN TO USE IT Use a function type expression for the majority of cases, like defining the type for a callback or a simple function variable. Use a call signature when you need to model a function that also has static-like properties, such as a utility function with a version property. Use a construct signature when typing class constructors or factory functions.

WHEN NOT TO USE IT Avoid using a function type expression if you need to describe properties on the function itself; a call signature is the correct tool. Conversely, don't use the more verbose call signature syntax when a simple function type expression is sufficient.

ONE CANONICAL EXAMPLE To describe a function that is callable and has a description property, you use a call signature:

type DescribableFunction = { description: string; (someArg: number): boolean; };

function doSomething(fn: DescribableFunction) { console.log(fn.description + " returned " + fn(6)); }

This allows you to pass a function that satisfies both conditions: it's callable with a number and returns a boolean, and it has a string property named description.

Read the original → typescriptlang.org

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.