[Typescript user manual] function

🚀 [TypeScript Introduction Manual] records the concept of Ts with high attendance rate, which is designed to help you understand and be familiar with Ts
🎉 This series will continue to be updated and corrected, focusing on the points of interest to everyone. Welcome to leave messages and exchange, and encourage each other on the way to advancement!
star this project gives the author some encouragement

1, Function declaration

Functions are first-class citizens. In JavaScript, there are two common ways to define functions - function declarations and function expressions. Let's learn how to write types describing functions.

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

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

The simplest way to describe a function is to use * * function type expression** It is written a bit like an arrow function:

function greeter(fn: (a: string) => void) {
  fn("Hello, World");
}
 
function printToConsole(s: string) {
  console.log(s);
}
 
greeter(printToConsole);

Syntax (A: String) = > void means that a function has a parameter named a and the type is string. This function does not return any value.

If the type of a function parameter is not explicitly given, it is implicitly set to any.

Note that the name of the function parameter is required. This function type description (string) = > void means that a function has a parameter of type any and named string.

Of course, we can also use type alias to define a function type:

type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
    // ...
}

1.1 function declaration

A function has input and output, which should be constrained in TypeScript. Both should be considered. The type definition of function declaration is relatively simple:

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

It is not allowed to enter redundant (or less than required) parameters:

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

sum(1);
// Expected 2 parameters, but got 1. ts(2554)
// index.ts(1, 25): the argument of "y" is not provided.

sum(1, 2, 3); // Expected 2 parameters, but got 3

1.2 function expression

The simplest way to describe a function is a function type expression. What should it be?

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

This code does not report errors and is correct. Another function expression is defined as follows:

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

Note: this arrow is not the well-known arrow function in ES6

The difference between the two is that the type of mySum1 is derived, while mySum2 is directly defined, but the two are actually the same and need no further study.

2, Function signature

In JavaScript, in addition to being called, functions can also have attribute values. However, the function type expression mentioned in the previous section does not support declaring attributes. If we want to describe a function with attributes, we can write a call signature in an object type.

type DescribableFunction = {
    description: string;
    (someArg: number): boolean; // Parameter list and return value type
};
function doSomething(fn: DescribableFunction) {
    console.log(fn.description + " returned " + fn(6));
}

// Test func
const func = function(someArg: number): boolean {
    return someArg > 5;
};
func.description = "Description carried";

doSomething(func);

3, Generic function

  1. It refers to a feature that does not specify a specific type in advance when defining a function, interface or class, but specifies a type when using it. remember
  2. The so-called genericity is to associate two or more values with the same type.

We often need to write this kind of function, that is, the output type of the function depends on the input type of the function, or the two input types are related to each other in some form. Let's consider a function that returns the first element of the array:

function firstElement(arr: any[]) {
    return arr[0];
}

Note that at this time, the type of the return value of the function is any. It would be better if the specific type of the first element could be returned.

At this point, we need to declare a type parameter in the function signature:

function firstElement<T>(arr: T[]): T {
    return arr[0];
}

const str = firstElement(['str']);
// const str: string
const bool = firstElement([true]);
// const bool: boolean
const num = firstElement([1]);
// const num: number

By adding a type parameter T to the function and using it in two places, we create an association between the function's input (i.e. array) and the function's output (i.e. return value). Now when we call it, a more specific type will be determined;

3.1 Inference

Note that in the above example, we did not explicitly specify the type of T, and the type is automatically inferred by TypeScript.

We can also use multiple type parameters, for example:

const res = map([1, 2, 3, 4], (val) => val.split(","));
// (parameter) val: number
// Property 'split' does not exist on type 'number'.

Note that in this example, TypeScript can infer that the type of Input is number. Naturally, split method cannot be used at this time.

3.2 Constraints

Sometimes, we want to associate two values, but we can only operate on some fixed fields of values. In this case, we can use * * constraint * * to limit the type parameters.

Let's write a function that returns the longer of the two values. To do this, we need to ensure that the passed in value has a length attribute of type number. We use the extends syntax to constrain function parameters:

function longest<Type extends { length: number }>(a: Type, b: Type) {
    if (a.length >= b.length) {
        return a;
    } else {
        return b;
    }
}

const res1 = longest([1, 2, 3], 1);
// ❌  Parameters of type 'number' cannot be assigned to type '{length: number;}' Parameters for.
const res2 = longest([1, "2", 3], [1]);
// const res2: (string | number)[]
const res3 = longest('yuguang', 'Yu Guang');
// const res3: "yuguang" | "Yu Guang"

It is precisely because we have made a {length: number} restriction on Type that we can be allowed to obtain the parameters of a and B The length property. Without this Type constraint, we can't even get these attributes, because these values may be of other types and have no length attribute.

Based on the parameters passed in, the types in longerArray and longerString are inferred.

3.3 Working with Constrained Values

This is a common error when using generic constraints:

function minimumLength<Type extends { length: number }>(
    obj: Type,
    minimum: number
): Type {
    if (obj.length >= minimum) {
        return obj;
    } else {
        return { length: minimum };
        // Cannot Type '{length: number;}' Assigned to Type 'Type'.
        //  "{ length: number; }"  Constraints that can be assigned to Type
        // However, the constraint '{length: number;}' can be used Instantiate 'Type' for other subtypes of.
    }
}

It's very winding here. I've understood it for a long time. Welcome to discuss and communicate

The problem is that functions should return objects of the same type as the passed in parameters, not just objects that comply with constraints. We can write such a counterexample:

3.4 Specifying Type Arguments

TypeScript can usually automatically infer the type parameters passed in generic calls, but it can't always infer them. For example, there is a function that combines two arrays:

function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
    return arr1.concat(arr2);
}
// If you call a function like this, an error will occur:

const arr1 = combine([1, 2, 3], ["hello"]);
// Type 'string' cannot be assigned to type 'number'

// If you insist on doing so, you can manually specify the Type:
const arr2 = combine<string | number>([1, 2, 3], ["hello"]);

4, Some suggestions for writing a good generic function

Although it's fun to write generic functions, it's also easy to roll over. If you use too many type parameters or use some unnecessary constraints, it may lead to incorrect type inference.

4.1 Push Type Parameters Down

The following two functions are written in a similar way:

function firstElement1<Type>(arr: Type[]) {
    return arr[0];
}

function firstElement2<Type extends any[]>(arr: Type) {
    return arr[0];
}

// a: number (good)
const a = firstElement1([1, 2, 3]);
// b: any (bad)
const b = firstElement2([1, 2, 3]);

analysis:

  • The first function can infer that the return type is number, but the second function infers that the return type is any, because TypeScript has to infer the arr[0] expression with the constraint type instead of inferring this element when the function is called.

4.2 use fewer type parameters

This is another pair of functions that look very similar:

// good
function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
  return arr.filter(func);
}
// bad
function filter2<Type, Func extends (arg: Type) => boolean>(
  arr: Type[],
  func: Func
): Type[] {
  return arr.filter(func);
}

We created a type parameter func with the no associated two values, which is a red flag because it means that caller has to manually specify an additional type parameter for no reason. Func does nothing, but makes the function more difficult to read and infer.

4.3 Type Parameters Should Appear Twice

Sometimes we forget that a function doesn't need generics

// bad
function greet<Str extends string>(s: Str) {
    console.log("Hello, " + s);
}
// good
function greet(s: string) {
    console.log("Hello, " + s);
}

Remember: type parameters are used to associate types between multiple values. If a type parameter appears only once in the function signature, it is not associated with anything. If a type parameter only appears in one place, it is strongly recommended that you reconsider whether you really need it.

5, Function parameters

  1. Optional parameters
  2. Parameter defaults
  3. Remaining parameters
  4. Parametric deconstruction

5.1 optional parameters

If you have declared the parameter type of the function, it is not allowed to enter redundant (or less than the required) parameters. So how to define optional parameters?

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

function hello(name1: string, name2: string): void {
    console.log(`hello! ${name1} ${name2 ? "and" + " " + name2 : ""}`);
}

hello("Yu Guang");
// Expected 2 parameters, but got 1
// Argument for 'name2' was not provided.

function hello1(name1: string, name2?: string): void {
    console.log(`hello! ${name1} ${name2 ? "and" + " " + name2 : ""}`);
}

hello1("Yu Guang");

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

5.2 parameter default value

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

function hello(name1: string = "Yu Guang", name2: string = "yuguang"): void {
    console.log(`hello! ${name1} ${name2 ? "and" + " " + name2 : ""}`);
}

hello(); // hello!  Yu Guang
hello("Xiao Ming"); // hello!  Xiaoming and yuguang

Is it back to the field we are familiar with?

5.3 remaining parameters

In ES6, you can use... Rest to obtain the remaining parameters in the function (rest parameters):

function func(a, ...arg) {
  console.log(a);
  console.log(arg);
}

func(1, 2, 3, 4); // 1, [2, 3, 4]

**Note: * * rest parameter can only be the last parameter

5.4 parameter deconstruction

You can use parameter deconstruction to easily deconstruct the object provided as a parameter into one or more local variables in the function body. In JavaScript, it is as follows:

type Man = { name: string; age: number };
function sum({ name, age }: Man) {
    console.log(name + age);
}
sum({ a: 10, b: 3, c: 9 });
// Type "{a: number; b: number; c: number;}" The parameter of cannot be assigned to a parameter of type 'Man'.
// Object text can specify only known attributes and "a" is not in type "Man". ts

After deconstructing the syntax, write the type annotation of the object.

6, Other types

Here are some common types. Like other types, you can use them anywhere, but they are often used with functions.

6.1 void

void means that a function does not return any value. This type should be used when the function does not return any value or cannot return an explicit value.

function noop() {
    return;
}

In JavaScript, a function does not return any value and implicitly returns undefined, but void and undefined are different in TypeScript.

6.2 object

This special type object can represent any value (string, number, bigint, boolean, symbol, null, undefined) that is not the original type. Object is different from the empty object type {} and the global type object. It's likely that you won't use object either.

Object is different from object. Always use object!

6.3 unknown

The unknown type can represent any value. It's a bit like any, but it's safer because it's illegal to do anything with a value of type unknown:

function f1(a: any) {
    a.b(); // OK
}
function f2(a: unknown) {
    a.b();
    // Property 'b' does not exist on type 'unknown'
}

Sometimes it is useful to describe function types. You can describe a function that can accept any value passed in, but you don't need any value of type any in the function body.

You can describe a function that returns a value of unknown type, such as:

function safeParse(s: string): unknown {
    return JSON.parse(s);
}

6.4 never

Some functions never return values:

function fail(msg: string): never {
  throw new Error(msg);
}

The never type indicates that a value is no longer observed.

As a return type, it means that the function will throw an exception or end the execution of the program.

When TypeScript determines that there is no possible type in the union type, the never type will also appear:

function fn(x: string | number) {
    if (typeof x === "string") {
        // do something
    } else if (typeof x === "number") {
        // do something else
    } else {
        x; // has type 'never'!
    }
}

Write at the end

This article is the third chapter of introduction to Typescript, which ends here. It mainly takes you to understand the performance of functions in Ts. in fact, we have talked about the basic part of a language - type related knowledge. Let's go

reference resources:

About me:

  • Flower name: Yu Guang
  • Email: webbj97@163.com
  • csdn: Portal

Other precipitates:

Keywords: Javascript Front-end TypeScript

Added by ramesh_iridium on Fri, 07 Jan 2022 13:20:08 +0200