[TypeScript] 006 - type of function

Posted by Miri4413 on Sat, 25 Dec 2021 06:14:51 +0100

7. Type of function

Function is a first-class citizen in JavaScript!

Function declaration

In JavaScript, there are two common ways to define functions - Function Declaration and Function Expression:

// Function Declaration
function sum(x, y) {
    return x + y;
}

// Function Expression
let mySum = function (x, y) {
    return x + y;
};

A function has input and output. To constrain it in TypeScript, both input and output need to be considered. The type definition of function declaration is relatively simple:

function sum(x: number, y: number): number {
    return x + y;
}

Note that it is not allowed to enter redundant (or less than required) parameters:

function sum(x: number, y: number): number {
    return x + y;
}
sum(1, 2, 3);

// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
function sum(x: number, y: number): number {
    return x + y;
}
sum(1);

// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.

Function expression

If you want us to write a definition of Function Expression now, it may be written as follows:

let mySum = function (x: number, y: number): number {
    return x + y;
};

This can be compiled, but in fact, the above code only defines the type of anonymous functions on the right of the equal sign, while mySum on the left of the equal sign is inferred by type inference through assignment operation. If we need to manually add a type to mySum, it should be as follows:

It seems so troublesome!

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};

Be careful not to confuse = > in TypeScript with = > in ES6.

In the type definition of TypeScript, = > is used to represent the function definition. On the left is the input type, which needs to be enclosed in parentheses, and on the right is the output type.

In ES6, = > is called arrow function, which is widely used and can be referred to Arrow function in ES6.

Defining the shape of a function with an interface

Shape? It's really a template! The following writing looks like a kind of encapsulation!

We can also use the interface to define the shape that a function needs to conform to:

interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    return source.search(subString) !== -1;
}

When the function expression | interface is used to define the function, type restrictions are carried out on the left side of the peer sign to ensure that the number of parameters, parameter types and return value types remain unchanged when assigning the function name in the future.

Optional parameters

As mentioned earlier, it is not allowed to enter redundant (or less than required) parameters. How to define optional parameters?

Similar to the optional attributes in the interface, we use? Indicates optional parameters:

function buildName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

Note that optional parameters must be followed by required parameters. In other words, required parameters are not allowed after optional parameters:

function buildName(firstName?: string, lastName: string) {
    if (firstName) {
        return firstName + ' ' + lastName;
    } else {
        return lastName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName(undefined, 'Tom');

// index.ts(1,40): error TS1016: A required parameter cannot follow an optional parameter.

Parameter defaults

In ES6, we allow you to add default values to function parameters. TypeScript will recognize the parameters with default values as optional parameters:

function buildName(firstName: string, lastName: string = 'Cat') {
    return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

At this time, it is not limited by "optional parameters must be followed by required parameters":

function buildName(firstName: string = 'Tom', lastName: string) {
    return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');

For default parameters, refer to Default values of function parameters in ES6.

Remaining parameters

In ES6, you can use Get the remaining parameters in the function by rest (rest parameters):

function push(array, ...items) {
    items.forEach(function(item) {
        array.push(item);
    });
}

let a: any[] = [];
push(a, 1, 2, 3);

In fact, items is an array. So we can define it by the type of array:

function push(array: any[], ...items: any[]) {
    items.forEach(function(item) {
        array.push(item);
    });
}

let a = [];
push(a, 1, 2, 3);

Note that the rest parameter can only be the last parameter. For rest parameters, please refer to rest parameter in ES6.

heavy load

Overloading allows a function to do different things when it accepts different numbers or types of parameters.

For example, we need to implement a function reverse. When inputting the number 123, we output the inverted number 321. When inputting the string 'hello', we output the inverted string 'olleh'.

Using union types, we can achieve this:

function reverse(x: number | string): number | string | void {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

However, this has a disadvantage that it can not be accurately expressed. When the input is a number, the output should also be a number, and when the input is a string, the output should also be a string.

In this case, we can use overloading to define multiple function types of reverse:

It doesn't look so simple!

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

In the above example, we have repeatedly defined the function reverse several times. The first few times are the function definitions, and the last time is the function implementation. In the code prompt of the editor, you can correctly see the first two prompts.

Note that TypeScript will preferentially match from the first function definition, so if multiple function definitions have an inclusion relationship, you need to write the exact definition first.

Topics: Javascript TypeScript