Function signatures
The TypeScript syntactic sugar that enforces strong typing on normal variables can also be used with callback functions. In order to do this, TypeScript introduces a new syntax, named the fat arrow syntax, () =>. When the fat arrow syntax is used, it means that one of the parameters to a function needs to be another function. Let's take a closer look at what this means. We will rewrite our previous JavaScript callback sample in TypeScript, as follows:
function callbackFunction(text: string) { console.log(`inside callbackFunction ${text}`); }
We start with the initial callback function, which takes a single text parameter, and logs a message to the console when this function is called. We can then define the doSomethingWithACallback function, as follows:
function doSomethingWithACallback( initialText: string, callback : (initialText: string) => void ) { console.log(`inside doSomethingWithCallback ${initialText}`); callback(initialText); }
Here, we have defined our doSomethingWithACallback function with two parameters. The first parameter is initialText, and is of type string. The second parameter is named callback, and now uses the fat arrow syntax to indicate that this parameter must be a function. Let's take a look at this syntax in a little more detail:
callback: (initialText: string) => void
The callback argument used here is typed (by the : syntax) to be a function, by using the fat arrow syntax, () =>. Additionally, this function takes a parameter named initialText that is of type string. To the right of the fat arrow syntax, we can see a new TypeScript basic type, called void. void is a keyword that is used to denote that a function does not return a value.
So, the doSomethingWithACallback function will only accept, as its second argument, a function that takes a single string parameter and returns void.
We can then use this function as follows:
doSomethingWithACallback("myText"", callbackFunction);
This code snippet is the same as was used in our JavaScript sample earlier. TypeScript will check the type of the callbackFunction parameter that was passed in, and ensure that it is, in fact, a function that accepts a single string as an argument, and does not return anything. If we try to invoke this doSomethingWithACallback incorrectly, say with two strings, as follows:
doSomethingWithACallback("myText", "this is not a function");
The compiler will generate the following message:
error TS2345: Argument of type '"this is not a function"' is not assignable to parameter of type '(initialText: string) => void'.
This error message is clearly stating that the second argument, which is "this is not a function", is not a function of the type (initialText: string) => void, as expected.
Given this function signature for the callback parameter, the following code would also generate compile time errors:
function callbackFunctionWithNumber(arg1: number) { console.log(`inside callbackFunctionWithNumber ${arg1}`) } doSomethingWithACallback("myText"", callbackFunctionWithNumber);
Here, we are defining a function named callBackFunctionWithNumber, that takes a number as its only parameter. When we attempt to compile this code, we will get an error message indicating that the callback parameter, which is now our callBackFunctionWithNumber function, also does not have the correct function signature, as follows:
error TS2345: Argument of type '(arg1: number) => void' is not assignable to parameter of type '(initialText: string) => void'.
This error message is clearly stating that a parameter of type (initialText: string) => void is expected, but an argument of type (arg1: number) => void was used instead.
This is a very powerful feature of TypeScript – defining in code what the signatures of functions should be, and warning users when they do not call a function with the correct parameters. Defining types for functions also allows the IDE we are working in to offer Intellisense features that will notify us of the correct usage of a particular function as and when we are using it.
As we saw in our introduction to TypeScript, this can also be significant when we are working with third-party libraries. Instead of needing to keep a copy of the documentation handy when working with a library, we can use Intellisense features in a more powerful way. However, before we are able to use third-party functions, classes, or objects in TypeScript, we need to define what their function signatures are. These function definitions are put into a special type of TypeScript file, called a declaration file, and saved with a .d.ts extension. We will take an in-depth look at declaration files in Chapter 5, Declaration Files and Strict Compiler Options.