Function Types describe the parameter and return types of a function i.e. they describe the function. Typescript has a built-in global type Function, which is a generic type that we can use to describe all functions. In this tutorial, we will learn how to create a type for a function using Function Type Expressions and Function Call Signatures, etc.
Table of Contents
Function Type
The following code results in a compiler error. Because we declared num to be a number but tried to assign a string to it.
1 2 3 4 | let num:number num="" //Type 'string' is not assignable to type 'number'. |
Now take a look at the following code. We are assigning a function to the variable sumFn
. However, we have not assigned a type to sumFn
. The compiler will assigns any
type to it, which means you can assign anything to it.
1 2 3 4 5 6 7 8 9 | let sumFn; sumFn = function(a:number, b:number) { return a+b; } sumFn=10; //No Error here because sumFn is of type any. |
How do we ensure that it only stores a function?
Typescript has a data type for it and it is called Function
.
Global Type Function
TypeScript has a global data type Function. You can use it to annotate a variable just like any other data type. In the following example, we annotate sumFn
with the data type Function. Now you can only assign a function to it and nothing else.
1 2 3 4 5 6 7 8 9 | let sumFn:Function //Function is a data type like string, object, number etc sumFn = function(a:number, b:number) { return a+b; } sumFn=10; //Type 'number' is not assignable to type 'Function' |
The Function data type describes properties like bind, call, apply, etc. It is a generic type that applies to all functions. But it does not provide any information about the parameters and return type of the function.
For example, assigning type Function to sumFn
prevents us from assigning anything other than function to it. But it will not stop us from assigning functions with different parameters and return types.
1 2 3 4 5 6 7 8 9 10 11 | let sumFn:Function sumFn = function(a:number, b:number) { return a+b; } sumFn = function(a:number, b:number,c:number) { return (a+b+c)/3; } |
How do we stop such assignments?
We can do that by creating a type that describes the Parameter and Return Type of a function. i.e. we create custom Type for the functions
There are three ways in which we can create a function type in TypeScript.
- Function Type Expression
- Function Call Signature
- Construct Signature
Function Type Expressions
Function Type expressions or Function type literals are the simplest way in which you can create a function type. It consists of two parts. List of Parameters and a Return type. The following is the syntax of the function type.
1 2 3 | (para1: type, para2:type, ...) => type |
Where para1, para2, etc are the parameters of the function. We need to give names to the parameters. But the typescript compiler ignores the names while checking if the two function types are compatible. The parameters must be enclosed inside the bracket.
It is followed by a => and the return type. Specifying the return type is mandatory. If the function does not return anything use void
as the return type.
The following is an example of a function type that accepts two numbers (num1 & num2) and returns a number.
1 2 3 | (num1: number, num2:number) => number |
We can use it to annotate any variable. In the following code, we assign the function type to a variable named addFn
.
1 2 3 | let addFn: (num1: number, num2:number) => number |
We can now assign any function which accepts two number parameters and returns a number to the variable addFn
. The compiler will not complain as long as the function signature matches. This is because typescript follows what is known as structural typing. In the following code, we have assigned a function that multiplies two numbers to addFn
. The compiler will not throw errors because the function call signature matches the type of addFn
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | let addFn:(num1: number, num2:number) => number addFn= function(a:number,b:number) { return a+b; } console.log(addFn(10,10)) //20 //Assigning a function which multiplies. //No error here since the function signature matches addFn= function(a:number,b:number) { return a*b; } console.log(addFn(10,10)) //100 |
But when we assign a function with a different signature compiler throws an error. In the following example, we are trying to assign a function that accepts three-parameter to the addFn
variable.
1 2 3 4 5 6 7 8 | let addFn:(num1: number, num2:number) => number addFn= function(a:number,b:number,c:number) { return a+b+c; } //Type '(a: number, b: number, c: number) => number' is not assignable to type '(num1: number, num2: number) => number'. |
You can omit the type of parameters (a
& b
) when creating the function. They are automatically inferred by the compiler.
1 2 3 4 5 6 7 | let addFn:(num1: number, num2:number) => number addFn= function(a,b) { //Types omitted. Infered as number by the TypeScript Compiler return a+b; } |
You can also assign a function when declaring the variable as shown below
1 2 3 4 5 6 | let addFn:(num1: number, num2:number) => number = function(a:number,b:number) { return a+b; } |
Again you can omit the types as they are inferred.
1 2 3 4 5 6 | let addFn:(num1: number, num2:number) => number = function(a,b) { return a+b; } |
Use void if the function does not return
1 2 3 | let addFn:(num1: number, num2:number) => void |
Type Alias
You can use a Type Alias to give a name to the type and re-use it.
1 2 3 4 5 | type fnAdd = (num1: number, num2:number) => number let sum:fnAdd = (a, b) => { return a * b; }, |
We cannot use an Interface to describe a function using function type expression. Because Interface describes an object. To use an Interface to describe the function, we need to use the function call signature inside an object.
Type Inference of Function Types
Typescript infers the type when we assign a function expression to a variable.
1 2 3 4 5 6 7 | let avgFn = function getAverage(a: number, b: number, c: number): number { const total = a + b + c; const average = total / 3; return average; } |
Hovering the mouse over the avgFn
will show you the inferred type of avgFn
.
Function Call Signatures
The syntax for creating Function Call Signatures is very similar to the Function Type Expression. The only difference is that call signatures use :
between the parameter list and the return type while the function type expressions use =>
.
1 2 3 | (para1: type, para2:type, ...) : type |
Call Signatures are used in an object type. Hence we can use it to describe
- Function
- Methods of an object or class.
- Functions with a property.
Functions
To describe a function as a property of an object first use curly brackets {}
to indicate that it is an object. Inside the curly braces { } include the function call signature. Note that we cannot use Function type expressions inside the {}
.
The following is an example usage of a call signature that describes a function.
1 2 3 4 5 6 7 8 9 10 11 | let addFn: { (num1: number, num2:number): number; //function call signature }; addFn = function (a,b) { return a+b; } console.log(addFn(10,10)) |
The following codes describe the function using both function type expression and function call signature.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //Using function type expression to describe function let addFn1 : (num1: number, num2:number) => number; //Using function call signature to describe function let addFn2 : { (num1: number, num2:number): number; }; addFn1 = function (a,b) { return a+b; } addFn2 = function (a,b) { return a+b; } addFn2 = addFn1 //Both has the same type console.log(addFn1(10,10)) //20 console.log(addFn2(100,100)) //200 |
You can use a Type Alias or Interface to name the function type.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | //Type Alias type tAddFn = { (num1: number, num2:number): number; }; //Interface interface iAddFn { (num1: number, num2:number): number; }; let addFn1:tAddFn = function (a,b) { return a+b; } let addFn2:iAddFn = function (a,b) { return a+b; } addFn2 = addFn1 console.log(addFn1(10,10)) //20 console.log(addFn2(100,100)) //200 |
Method of an object/class
The following code shows how to describe a method when we describe an object type. We describe the method by stating the method name followed by the call signature.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | let product : { id:number; rate:number calcPrice(qty:number): number //method name following by call signature } //Ok product = { id:1, rate:10, calcPrice(qty:number) { return this.rate*qty } } //Error //Type '(qty: number, taxRate: number) => number' is not assignable to type '(qty: number) => number'. product = { id:1, rate:10, calcPrice(qty:number, taxRate:number) { return (this.rate*qty) + (this.rate * qty *taxRate/100) } } //Error //Type '{ id: number; rate: number; getPrice(qty: number): number; }' is not assignable to type '{ id: number; rate: number; calcPrice(qty: number): number; }'. // Object literal may only specify known properties, and 'getPrice' does not exist in type '{ id: number; rate: number; calcPrice(qty: number): number; }' product = { id:1, rate:10, getPrice(qty:number) { return this.rate*qty } } |
Use an interface or type alias to assign name to the type.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | interface IProduct { id:number; rate:number calcPrice(qty:number): number } type TProduct = { id:number; rate:number calcPrice(qty:number): number } let prd1:IProduct = { id:1, rate:10, calcPrice(qty:number) { return this.rate*qty } } let prd2:TProduct = { id:1, rate:10, calcPrice(qty:number) { return this.rate*qty } } |
Functions with Property
Functions are objects, hence they also can have properties. We can use the call signatures to create a type for them. The following addFn
has a foo
property. We use the Object.assign(target, source) to create an instance from the type.
1 2 3 4 5 6 7 8 9 10 11 12 13 | let addFn : { (num1: number, num2:number): number; foo:string }; addFn= Object.assign( (a:number,b:number) => {return a+b}, {foo:''} ) console.log(addFn(10,10)) //20 |
Construct Signatures
We can also call the functions with new
the operator to create a new object. Such functions are known as constructor functions. We can use the Construct Signatures to describe its parameter list and return type. The syntax is identical to call signatures, except that we write a new keyword in front of the call signature.
1 2 3 4 5 6 7 8 9 10 11 12 | interface Point { x: number; y: number; } interface PointConstructor { new(x: number, y: number): Point; //Construct Signature } |
You can read more about them here and here