TypeScript Fundamentals - Functions
Functions in TypeScript just like in JavaScript are the building blocks for all programs in this ecosystem. They play a key role in describing how to do things in the language. With parameter annotations and return type annotations, we can type all functions to have predictable inputs and outputs. This comes in handy in JavaScript where variables can be dynamic and unpredictable.
A fully typed function in TypeScript will have types on both the variable and on the function itself. Although this is fine, it makes it very verbose and adds to the keystrokes you have to write for your programs. TypeScript can infer the type of the function from the function if it is typed correctly. We can also use contextual typing
to create type inference where the function variable is typed but the function is not typed. This also reduces the amount of keystrokes needed to make our functions typed.
// Fully Typed Function
const multiply: (str: string, num: number) => string = function (
str: string,
num: number
): string {
return `${str} `.repeat(num).trim();
};
console.log(multiply('Jake', 3)); // Jake Jake Jake
// Break out the type
interface IMultiply {
(str: string, num: number): string;
}
const multiply: IMultiply = function (str: string, num: number): string {
return `${str} `.repeat(num).trim();
};
console.log(multiply('Jake', 3)); // Jake Jake Jake
// Infer function type from function definition
const multiply = function (str: string, num: number): string {
return `${str} `.repeat(num).trim();
};
console.log(multiply('Jake', 3)); // Jake Jake Jake
// Contextual typing inference
const multiply: (str: string, num: number) => string = function (str, num) {
return `${str} `.repeat(num).trim();
};
console.log(multiply('Jake', 3)); // Jake Jake Jake
Default and Optional Parameters
If we wanted to make the num
parameter in the function above optional, we can do that by adding (?) to the type. This will make it optional and not required when calling the function. However our function will fail without this argument. We can add a default parameter to the function for the num
argument which will pass a number to the function in case one is not given. Giving a default parameter makes that parameter optional in its type.
interface IMultiply {
(str: string, num?: number): string;
}
const multiply: IMultiply = function (str: string, num = 1): string {
return `${str} `.repeat(num).trim();
};
console.log(multiply('Jake')); // Jake
Rest Parameters
If working with multiple parameters at a time and we don’t know ahead of time how many parameters will be passed to our function, we can bundle all the dynamic parameters into an array and use that array within the function. The ellipsis (…) is used in the type of the function with rest parameters.
interface IShowNames {
(name: string, ...rest: string[]): string;
}
const showNames: IShowNames = function (
name: string,
...rest: string[]
): string {
return `${name} ${rest.join(' ')}`.trim();
};
console.log(showNames('Jake')); // Jake
console.log(showNames('Jake', 'Paul', 'Morgan')); // Jake Paul Morgan
Function Overloads
JavaScript being a dynamic language lends itself to situations where a function can return different objects based on the shape of the arguments passed to it. In this case we can overload the type for the function and give all the possible combinations of input parameters to account for the dynamic nature of the function.
let stocks = [
{ stock: 'AAPL', name: 'Apple', price: 150 },
{ stock: 'FB', name: 'Facebook', price: 84 },
{ stock: 'MSFT', name: 'Microsoft', price: 124 },
{ stock: 'GOOG', name: 'Google', price: 650 },
{ stock: 'WFC', name: 'Wells Fargo', price: 45 },
{ stock: 'GE', name: 'General Electric', price: 12 },
];
function pickStocks(x: string): { stock: string; name: string; price: number };
function pickStocks(x: number): { stock: string; name: string; price: number };
function pickStocks(x: string | number) {
if (typeof x === 'string') {
return stocks.find((stock) => stock.name === x);
} else if (typeof x === 'number') {
return stocks[Math.floor(Math.random() * stocks.length)];
}
}
console.log(pickStocks('Apple')); // { stock: 'AAPL', name: 'Apple', price: 150 }
console.log(pickStocks(43)); // Random value: { stock: 'GOOG', name: 'Google', price: 650 }
console.log(pickStocks([23])); // Error: No overload matches this call.
The pickStocks
function can either take a string or number as its argument and depending on what is passed, will return a stock object. We overload the type definition for the function giving all possible combinations of parameters and output types. When TypeScript evaluates the function, it starts from the top and goes down the list until it finds a suitable signature that matches the parameters given.